blob: 8906b6910a56fc7d36047d3bd652a3bdaaef18cb [file] [log] [blame] [raw]
/* hp_disclib.c: HP MAC/ICD disc controller simulator library
Copyright (c) 2011-2017, 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.
22-Apr-17 JDB A failed sim_fseek call now causes a drive fault
10-Oct-16 JDB Moved "hp3000_defs.h" inclusion from "hp_disclib.h"
03-Aug-16 JDB "fmt_bitset" now allows multiple concurrent calls
09-Jun-16 JDB Added casts for ptrdiff_t to int32 values
08-Jun-16 JDB Corrected %d format to %u for unsigned values
16-May-16 JDB DRIVE_PROPS.name is now a pointer-to-constant
13-May-16 JDB Modified for revised SCP API function parameter types
03-May-16 JDB Added a trace to identify the unit requesting attention
24-Mar-16 JDB Changed the buffer element type from uint16 to DL_BUFFER
21-Mar-16 JDB Changed uint16 types to HP_WORD
27-Jul-15 JDB First revised release version
21-Feb-15 JDB Revised for new controller interface model
24-Dec-14 JDB Added casts for explicit downward conversions
27-Oct-14 JDB Corrected the relative movement calculation in start_seek
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, August 1980)
- HP 13365 Integrated Controller Programming Guide
(13365-90901, February 1980)
- HP 1000 ICD/MAC Disc Diagnostic Reference Manual
(5955-4355, June 1984)
- RTE-IVB System Manager's Manual
(92068-90006, January 1983)
- DVR32 RTE Moving Head Driver Source
(92084-18711, Revision 5000)
The 13037 multiple-access disc controller (MAC) connects from one to eight HP
7905 (15 MB), 7906 (20 MB), 7920 (50 MB), or 7925 (120 MB) disc drives to
interfaces installed in from one to eight HP 1000, 2000, or 3000 CPUs. The
drives use a common command set and present data to the controller
synchronously at a data rate of 468.75 kilowords per second (2.133
microseconds per word).
The controller hardware consists of three PCAs: a 16-bit microprogrammed
processor constructed from 74S181 bit slices operating at 5 MHz, a device
controller that provides the interconnections to the drives and CPU
interfaces, and an error correction controller that enables the correction of
up to 32-bit error bursts. 1024 words of 24-bit firmware are stored in ROM
on the error correction PCA, and the execution time is 200 nanoseconds per
instruction.
The Integrated Controller Drive (ICD) models include the HP 7906H, 7920H, and
7925H. These drives are identical to the corresponding MAC drives, except
that they integrate a single-CPU version of the MAC controller on two PCAs
housed within the drive: an 8-bit microprocessor constructed from two 4-bit
slices operating at 3.75 MHz, and an 8-bit DMA that handles the data path
between the drive and CPU. Connection to the CPU is via the Hewlett-Packard
Interface Bus (HP-IB) -- HP's implementation of the IEEE-488 standard.
The ICD command set essentially is the MAC command set modified for
single-unit operation. The unit number and CPU hold bit fields in the opcode
words are unused in the ICD implementation. The Load TIO Register, Wakeup,
and Request Syndrome commands are removed, as Load TIO is used with the HP
3000, Wakeup is used in a multi-CPU environment, and the simpler ICD
controller does not support ECC. Controller status values 02B (Unit
Available) and 27B (Unit Unavailable) are dropped as the controller supports
only single units, 12B (I/O Program Error) is reused to indicate HP-IB
protocol errors, 13B (Sync Not Received) is added, and 17B (Possibly
Correctable Data Error) is removed as error correction is not supported.
Some minor redefinitions also occur. For example, status 14B (End of
Cylinder) is expanded to include an auto-seek beyond the drive limits, and
37B (Drive Attention) is restricted to just head unloads (from head loads and
unloads).
The MAC controller offers an HP-IB option: the HP 12745A Disc Controller to
HP-IB Adapter Kit. This card plugs into the 13037's chassis containing the
other three controller cards and connects to the CPU interface port of the
device controller PCA in place of the multi-CPU-interface cable. It allows
HP-IB 3000s and the HP 64000 Logic Development Station to connect to MAC disc
drives; the ICD drives are not supported on these systems.
This library provides the 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 HP2100 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.
The original release of this library did not handle the data transfer between
the controller and the interface directly. Instead, data was moved between
the interface and a sector buffer by the interface simulator, and then the
buffer was passed to the disc library for reading or writing. This buffer
was also used to pass disc commands and parameters to the controller, and to
receive status information from the controller.
While this approach served to allow the library to be shared between
dissimilar interfaces, each interface had to have intimate knowledge of the
internal controller state in order to schedule parameter and data transfers.
In particular, the unit service routine had to base its actions on specific
controller phase and opcode pairs and manipulate the internal state variables
of the controller. As such, the library could not be viewed opaquely.
In addition, the HP 3000 interface required channel program support that was
not provided by the simulation library although it was present in hardware.
Adapting the existing library model would have placed an even larger and more
intimate burden on the interface simulation.
As a result, the library API was rewritten to model the hardware more
closely and provide a more strict separation of controller and interface
functions. Instead of providing separate routines to prepare, start, and end
commands, service units, and poll drives for Attention status, the new model
provides a single routine that represents the hardware data, flag, and
function buses between the interface and controller. The interface calls
this routine whenever the state of the flag bus changes, and the controller
responds by potentially changing the data and function buses. The interface
merely responds to those changes without requiring any other knowledge of the
internal state of the controller.
A device interface simulator interacts with the disc controller simulator via
the dl_controller routine, which simulates the command, status, and data
interconnection between the interface and controller. Utility routines are
also provided to attach and detach disc image files from drive units, load or
unload the drive's heads, set drive model and protection status, select the
interface timing mode (real or fast), and enable overriding of disc command
status returns for diagnostics.
In hardware, the interface and controller are interconnected via a 16-bit
bidirectional data bus (IBUS), a 6-bit flag bus and one signal (CLEAR) to
the controller, and a 4-bit function bus (IFNBUS) and four signals (ENID,
ENIR, IFVLD, and IFCLK) to the interface. The interface initiates controller
action by changing the state of the flag bus, and the controller responds by
asserting a function on the function bus. For example, the interface starts
a disc command by asserting CMRDY on the flag bus. The controller responds
by placing the IFGTC (Interface Get Command) function on the function bus and
asserting the ENID (Enable Interface Drivers) and IFVLD (Interface Function
Valid) signals. The interface then replies by placing the command on the
data bus, where it is read by the controller. The controller then decodes
the command and initiates processing.
The controller microprogram runs continuously. However, command execution
pauses in various wait loops whenever the controller must suspend until an
external event occurs. For example, an Address Record command waits first
for the CPU to send the cylinder address and then waits again for the CPU to
send the head/sector address. The controller then saves these values in
registers before completing the command and then waiting for the CPU to send
a new command.
In simulation, the dl_controller routine is called with a set of flags and
the content of the data bus whenever the flag state changes or a service
event occurs. The routine returns a set of functions and the new content of
the data bus. To use the above example, dl_controller would be called with
CMRDY and the command word and would return IFGTC; the data bus return value
would not be used in this case. In hardware, the controller might send a
series of individual functions to the interface in response to a single
invocation. In simulation, this series would be collected into a single
function set for return.
Hardware wait loops are simulated by the dl_controller routine returning to
the caller until the expected external event occurs. In the Address Record
example, dl_controller would be called first when the command is issued by
the CPU. The routine would initiate command processing and then return to
wait for the cylinder address. When the CPU provided the address, the
interface simulator would call dl_controller again with the cylinder value.
The routine would then return to wait for the head/sector address. When it
was available, dl_controller would be called with the value, and the routine
would complete the command and return to the caller to wait for a new
command. So, in simulation, the controller only "runs" when it has work to
do.
A controller instance is represented by a CNTLR_VARS structure, which
maintains the controller's internal state. A MAC interface will have a
single controller instance that controls up to eight drive units, whereas an
ICD interface will have one controller instance per drive unit. The minor
differences in controller action between the two are handled internally.
The interface simulator must declare one unit for each disc drive to be
controlled by the library. For a MAC controller, eight units are required,
plus one additional unit for the controller itself. For an ICD controller,
only one unit is required.
The controller maintains five values in each drive's unit structure:
u3 (CYL) -- the current drive cylinder
u4 (STATUS) -- the drive status (Status-2)
u5 (OPCODE) -- the drive current operation in process
u6 (PHASE) -- the drive current operation phase
pos -- the current byte offset into the disc image file
Drives maintain their cylinder (head positioner) locations separate from the
cylinder location stored in the controller. This allows the controller to
implement sparing by positioning to one location while storing a different
location in the sector headers. It also allows seek retries by issuing a
Recalibrate (which moves the positioner to cylinder 0) followed by the
original read or write (which repositioned to the cylinder stored in the
controller).
The drive status field contains only a subset of the status maintained by
drives in hardware. Specifically, the Attention, Read-Only, First Status,
Fault, and Seek Check bits are stored in the status field. The other bits
(Format Enabled, Not Ready, and Drive Busy) are set dynamically whenever
status is requested.
Per-drive opcode and phase values allow seeks to be overlapped. For example,
a Seek issued to unit 0 may be followed by a Read issued to unit 1. When the
seek completes on unit 0, its opcode and phase values will let the controller
set the appropriate seek completion status without disturbing the values
currently in-use by unit 1.
The simulation defines these command phases:
Idle -- the unit is not currently executing a command
Parameter -- the unit is obtaining or returning parameter values
Seek -- the unit is seeking to a new head position
Rotate -- the unit is rotating into position to access a sector
Data -- the unit is obtaining or returning sector data values
Intersector -- the unit is rotating between sectors
End -- the unit is completing a command
A value represents the current state of the unit. If a unit is active, the
phase will end when the unit is serviced.
In addition to the controller structure(s), an interface declares a data
buffer to be used for sector transfers. 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. Only one
buffer is needed per interface, regardless of the number of controllers or
units handled, as a single interface cannot perform data transfers on one
drive concurrently with a command directed to another drive.
An interface is also responsible for declaring a structure of type
DELAY_PROPS that contains the timing values for the controller when in
FASTTIME mode. The values are event counts for these actions:
- track-to-track seek time
- full-stroke seek time
- full sector rotation time
- per-word data transfer time
- intersector gap time
- controller execution overhead time
These values are typically exposed via the interface's register set and so
may be altered by the user. A macro, DELAY_INIT, is provided to initialize
the structure.
An interface may optionally declare an array of DIAG_ENTRY structures if it
wishes to use the diagnostic override capability. Diagnostic overrides are
used to return controller status values that otherwise are not simulated to a
diagnostic program. An example would be a Correctable Data Error or a
Head-Sector Miscompare. If this facility is to be used, a pointer to the
array is placed in the CNTLR_VARS structure when it is initialized, and the
array itself is initialized with the DL_OVEND value that indicates that no
overrides are currently defined.
If the pointer is set, then when each command is started, the cylinder, head,
sector, and opcode values from the current override entry are checked against
the corresponding current controller values. If a match occurs, then the
controller status and Spare/Protected/Defective values are set from the entry
rather than being cleared, and the pointer is moved to the next entry. These
values will then be returned as the completion status of the current command.
If the command performs address verification, then any SPD value(s) will be
used as the result of the verification. In particular, setting the P bit for
a Write will cause a Protected Track error if the FORMAT switch is not on,
and any status value other than Normal Completion, Correctable Data Error, or
Uncorrectable Data Error will cause a verification abort.
In hardware, the errors that may occur during verification are Cylinder
Miscompare, Head-Sector Miscompare, Sync Timeout, Illegal Spare Access, and
Defective Track; any of these errors may be simulated. In addition, an
Uncorrectable Data Error may occur in hardware if the controller is unable to
verify any of the 16 sectors starting at the sector preceding the target
sector, but this error cannot be simulated.
Specifying either a Correctable Data Error or an Uncorrectable Data Error
will cause an abort at the end of the first sector of a read, write, or
verify command.
Correctable Data Error and Uncorrectable Data Error may also be specified for
the Request Syndrome command. A table entry with the former status value is
always followed by an additional entry that contains the values to be
returned for the three syndrome words and the displacement.
The last defined table entry always contains a special end-of-table value.
The controller library provides a macro, DL_MODS, that initializes MTAB
entries, and two utility routines, dl_set_diag and dl_show_diag, that provide
a user interface for setting up a table of diagnostic overrides. See the
comments for these routines below for the command syntax.
A macro, CNTLR_INIT, is provided to initialize the controller structure from
the following parameters:
- the type of the controller (MAC or ICD)
- the simulation DEVICE structure on which the controller operates
- the data buffer array
- the diagnostic override array or NULL if not used
- the structure containing the FASTTIME values
A macro, DL_REGS, is also provided that initializes a set of REG structures
to provide a user interface to the controller structure.
In hardware, disc drives respond to commands issued by the controller. The
only unsolicited status from drives occurs when the heads are loaded or
unloaded by the operator. In simulation, SET <dev> UNLOAD and SET <dev> LOAD
commands represent these actions. The controller must be notified by calling
the dl_load_unload routine in response to changes in a disc drive's RUN/STOP
switch.
Finally, the controller library provides extensive tracing of its internal
operations via debug logging. Six debug flags are declared for use by the
interface simulator. When enabled, these report controller actions at
various levels of detail.
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 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.
4. In hardware, a command pending on an interface (CMRDY asserted) while a
previous command is executing will be started as soon as the current
command completes. In simulation, dl_controller will exit when the
current command completes and must be called again if CMRDY is asserted.
This is necessary to allow the completion status of the prior command to
be returned to the interface before the new command is started.
*/
#include <math.h>
#include "hp3000_defs.h" /* this must reflect the machine used */
#include "hp_disclib.h"
/* Program constants */
#define CNTLR_UNIT (DL_MAXDRIVE + 1) /* controller unit number */
#define MAX_UNIT 10 /* last legal unit number */
#define WORDS_PER_SECTOR 128 /* data words per sector */
#define UNTALK_DELAY 160 /* ICD untalk delay (constant instruction count) */
#define CNTLR_TIMEOUT S (1.74) /* command and parameter wait timeout (1.74 seconds) */
#define NO_EVENT -1 /* do not schedule an event */
#define NO_ACTION (CNTLR_IFN_IBUS) (NO_FUNCTIONS | NO_DATA)
/* Controller unit pointer */
#define CNTLR_UPTR (cvptr->device->units + cvptr->device->numunits - 1)
/* Unit flags accessor */
#define GET_MODEL(f) (DRIVE_TYPE) ((f) >> UNIT_MODEL_SHIFT & UNIT_MODEL_MASK)
/* Controller clear types */
typedef enum {
Hard_Clear, /* power-on/preset hard clear */
Timeout_Clear, /* command or parameter timeout clear */
Soft_Clear /* programmed soft clear */
} CNTLR_CLEAR;
/* Command accessors.
Disc commands are passed across the data bus to the controller with the CMRDY
flag asserted. The commands have several forms, depending on the particular
opcode:
15| 14 13 12| 11 10 9 | 8 7 6 | 5 4 3 | 2 1 0 HP 1000 numbering
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - | command opcode | - - - - - - - - | form 1
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - | command opcode | - - - - | unit number | form 2
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - | command opcode | H | - - - | unit number | form 3
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | P | D | command opcode | H | - - - | unit number | form 4
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - | command opcode | retries | D | S | C | A | form 5
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| - - - | command opcode | head | sector | form 6
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 HP 3000 numbering
Form 1 is used by the Address Record, Clear, End, Load TIO Register, Request
Disc Address, and Request Syndrome commands.
Form 2 is used by the Request Disc Sector and Request Status commands.
Form 3 is used by the Read, Read Full Sector, Read With Offset, Read Without
Verify, Recalibrate, Seek, Verify, Wakeup, Write, and Write Full Sector
commands, where:
H = hold the drive
Form 4 is used by the Initialize Command, where:
S = initialize the track to spare status
P = initialize the track to protected status
D = initialize the track to defective status
H = hold the drive
Form 5 is used by the Set File Mask command, where:
D = decremental seek
S = sparing enabled
C = cylinder mode
A = auto-seek enabled
Form 6 is used by the Cold Load Read command.
*/
#define CM_OPCODE_MASK 0017400u /* operation code mask */
#define CM_UNIT_MASK 0000017u /* unit number mask */
#define CM_SPARE 0100000u /* spare track */
#define CM_PROTECTED 0040000u /* protected track */
#define CM_DEFECTIVE 0020000u /* defective track */
#define CM_SPD_MASK (CM_SPARE | CM_PROTECTED | CM_DEFECTIVE)
#define CM_RETRY_MASK 0000360u /* retry count mask */
#define CM_FILE_MASK_MASK 0000017u /* file mask mask */
#define CM_DECR_SEEK 0000010u /* 0/1 = incremental/decremental seek */
#define CM_SPARE_EN 0000004u /* sparing enabled */
#define CM_CYL_MODE 0000002u /* 0/1 = surface/cylinder mode */
#define CM_AUTO_SEEK_EN 0000001u /* auto-seek enabled */
#define CM_HEAD_MASK 0000300u /* cold load read head mask */
#define CM_SECTOR_MASK 0000077u /* cold load read sector mask */
#define CM_OPCODE_SHIFT 8
#define CM_UNIT_SHIFT 0
#define CM_RETRY_SHIFT 4
#define CM_FILE_MASK_SHIFT 0
#define CM_HEAD_SHIFT 6
#define CM_SECTOR_SHIFT 0
#define CM_SPD(c) ((c) & CM_SPD_MASK)
#define CM_OPCODE(c) (CNTLR_OPCODE) (((c) & CM_OPCODE_MASK) >> CM_OPCODE_SHIFT)
#define CM_UNIT(c) (((c) & CM_UNIT_MASK) >> CM_UNIT_SHIFT)
#define CM_RETRY(c) (((c) & CM_RETRY_MASK) >> CM_RETRY_SHIFT)
#define CM_FILE_MASK(c) (((c) & CM_FILE_MASK_MASK) >> CM_FILE_MASK_SHIFT)
#define CM_HEAD(c) (((c) & CM_HEAD_MASK) >> CM_HEAD_SHIFT)
#define CM_SECTOR(c) (((c) & CM_SECTOR_MASK) >> CM_SECTOR_SHIFT)
static const BITSET_NAME file_mask_names [] = { /* File mask word */
"\1decremental seek\0incremental seek", /* bit 3/12 */
"sparing", /* bit 2/13 */
"\1cylinder mode\0surface mode", /* bit 1/14 */
"autoseek" /* bit 0/15 */
};
static const BITSET_FORMAT file_mask_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (file_mask_names, 0, msb_first, has_alt, append_bar) };
/* Parameter accessors.
Parameters are passed across the data bus to the controller with the DTRDY
flag asserted. The parameters have several forms, depending on the commands
requesting them:
15| 14 13 12| 11 10 9 | 8 7 6 | 5 4 3 | 2 1 0 HP 1000 numbering
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| cylinder | form 1 (in/out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | head | sector | form 2 (in/out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| S | P | D | status code | - - - - | unit number | form 3 (out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| E | - - | drive type | - | A | R | F | L | S | K | N | B | form 4 (out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 0 | sector | form 5 (out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| sector count | form 6 (in)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| displacement | form 7 (out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| pattern | form 8 (out)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| data word | form 9 (in)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | A | D | S | - | cyl offset magnitude | form 10 (in)
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0 | 1 2 3 | 4 5 6 | 7 8 9 |10 11 12 |13 14 15 HP 3000 numbering
Forms 1 and 2 are used by the Address Record, Request Disc Address, Request
Syndrome, and Seek commands.
Form 3 is used by the Request Status and Request Syndrome commands, where:
S = last track was a spare
P = last track was protected
D = last track was defective
Form 4 is used by the Request Status command, where:
E = error present
A = attention
R = read-only
F = format enabled
L = drive fault
S = first status
K = seek check
N = not ready
B = drive busy
Form 5 is used by the Request Sector Address command.
Form 6 is used by the Verify command.
Forms 7 and 8 are used by the Request Syndrome command.
Form 9 is used by the Load TIO Register command.
Form 10 is used by the Read With Offset command, where:
A = advance the clock (valid for 13037A only)
D = delay the clock (valid for 13037A only)
S = sign of cylinder offset
*/
#define S1_SPARE 0100000u /* spare track */
#define S1_PROTECTED 0040000u /* protected track */
#define S1_DEFECTIVE 0020000u /* defective track */
#define S1_STATUS_MASK 0017400u /* encoded termination status mask */
#define S1_UNIT_MASK 0000017u /* unit number mask */
#define S1_STATUS_SHIFT 8
#define S1_UNIT_SHIFT 0
#define S1_STATUS(n) ((n) << S1_STATUS_SHIFT & S1_STATUS_MASK)
#define S1_UNIT(n) ((n) << S1_UNIT_SHIFT & S1_UNIT_MASK)
#define S2_ERROR 0100000u /* any error */
#define S2_DRIVE_TYPE_MASK 0017000u /* drive type mask */
#define S2_ATTENTION 0000200u /* attention */
#define S2_READ_ONLY 0000100u /* read-only */
#define S2_FORMAT_EN 0000040u /* format enabled */
#define S2_FAULT 0000020u /* drive fault */
#define S2_FIRST_STATUS 0000010u /* first status */
#define S2_SEEK_CHECK 0000004u /* seek check */
#define S2_NOT_READY 0000002u /* not ready */
#define S2_BUSY 0000001u /* drive busy */
#define S2_STOPS (S2_FAULT \
| S2_SEEK_CHECK \
| S2_NOT_READY) /* bits that stop drive access */
#define S2_ERRORS (S2_FAULT \
| S2_SEEK_CHECK \
| S2_NOT_READY \
| S2_BUSY) /* bits that set S2_ERROR */
#define S2_CPS (S2_ATTENTION \
| S2_FAULT \
| S2_FIRST_STATUS \
| S2_SEEK_CHECK) /* bits that are cleared by Controller Preset */
#define S2_DRIVE_TYPE_SHIFT 9
#define S2_DRIVE_TYPE(n) ((n) << S2_DRIVE_TYPE_SHIFT & S2_DRIVE_TYPE_MASK)
#define S2_TO_DRIVE_TYPE(n) (((n) & S2_DRIVE_TYPE_MASK) >> S2_DRIVE_TYPE_SHIFT)
#define PIO_HEAD_MASK 0017400u /* head mask */
#define PIO_SECTOR_MASK 0000377u /* sector mask */
#define PI_ADV_CLOCK 0001000u /* advance clock */
#define PI_DEL_CLOCK 0000400u /* delay clock */
#define PI_NEG_OFFSET 0000200u /* 0/1 = positive/negative cylinder offset sign */
#define PI_OFFSET_MASK 0000077u /* cylinder offset mask */
#define PIO_HEAD_SHIFT 8
#define PIO_SECTOR_SHIFT 0
#define PI_OFFSET_SHIFT 0
#define PI_HEAD(p) (((p) & PIO_HEAD_MASK) >> PIO_HEAD_SHIFT)
#define PI_SECTOR(p) (((p) & PIO_SECTOR_MASK) >> PIO_SECTOR_SHIFT)
#define PI_OFFSET(p) (((p) & PI_OFFSET_MASK) >> PI_OFFSET_SHIFT)
#define PO_HEAD(n) (((n) << PIO_HEAD_SHIFT & PIO_HEAD_MASK))
#define PO_SECTOR(n) (((n) << PIO_SECTOR_SHIFT & PIO_SECTOR_MASK))
static const BITSET_NAME status_1_names [] = { /* Status-1 word */
"spare", /* bit 15/0 */
"protected", /* bit 14/1 */
"defective" /* bit 13/2 */
};
static const BITSET_FORMAT status_1_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (status_1_names, 13, msb_first, no_alt, append_bar) };
static const BITSET_FORMAT initialize_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (status_1_names, 13, msb_first, no_alt, no_bar) };
static const BITSET_NAME status_2_names [] = { /* Status-2 word */
"attention", /* bit 7/ 8 */
"read only", /* bit 6/ 9 */
"format enabled", /* bit 5/10 */
"fault", /* bit 4/11 */
"first status", /* bit 3/12 */
"seek check", /* bit 2/13 */
"not ready", /* bit 1/14 */
"busy" /* bit 0/15 */
};
static const BITSET_FORMAT status_2_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (status_2_names, 0, msb_first, no_alt, no_bar) };
static const BITSET_NAME offset_names [] = { /* Read With Offset parameter */
"advanced clock", /* bit 9/ 6 */
"delayed clock" /* bit 8/ 7 */
};
static const BITSET_FORMAT offset_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (offset_names, 8, msb_first, no_alt, append_bar) };
/* 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 a Seek command and the head/sector
address during an Address Record drive command).
In simulation, the model ID number from the unit flags is used as an index
into the drive properties 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 separate "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.
The Drive Type is reported by the controller in the second status word
(Status-2) returned by the Request Status command.
*/
typedef struct {
const char *name; /* drive name */
uint32 sectors; /* sectors per head */
uint32 heads; /* heads per cylinder*/
uint32 cylinders; /* cylinders per drive */
uint32 words; /* words per drive */
uint32 remov_heads; /* number of removable-platter heads */
uint32 fixed_heads; /* number of fixed-platter heads */
} DRIVE_PROPS;
static const DRIVE_PROPS drive_props [] = { /* indexed by DRIVE_TYPE */
/* drive sectors heads cylinders words remov fixed */
/* name per trk per cyl per drive per drive heads heads */
/* ------- ------- ------- ---------- ----------- ----- ----- */
{ "7906", 48, 4, 411, WORDS_7906, 2, 2 }, /* drive type 0 */
{ "7920", 48, 5, 823, WORDS_7920, 5, 0 }, /* drive type 1 */
{ "7905", 48, 3, 411, WORDS_7905, 2, 1 }, /* drive type 2 */
{ "7925", 64, 9, 823, WORDS_7925, 9, 0 } /* drive type 3 */
};
/* Delay properties table.
To support the realistic timing mode, the delay properties table contains
timing specifications for the supported disc drives. The times represent the
delays for mechanical and electronic operations. Delay values are in event
tick counts; macros are used to convert from times to ticks.
The drive type field differentiates between drive models available on a given
controller. The field is not significant for MAC and ICD controllers because
all of the drives supported have the same specifications.
The controller overhead values are estimates; they do not appear to be
documented for MAC and ICD controllers, although they are published for
various CS/80 drives.
*/
static const DELAY_PROPS real_times [] = {
/* cntlr drive seek seek sector data intersector cntlr */
/* type type trk-trk full rotation per word gap overhead */
/* ----- -------- ------- -------- ----------- ---------- ----------- -------- */
{ MAC, HP_All, mS (5), mS (45), uS (347.2), uS (2.13), uS (27.2), uS (200) },
{ ICD, HP_All, mS (5), mS (45), uS (347.2), uS (2.13), uS (27.2), mS (1.5) }
};
#define DELAY_COUNT (sizeof real_times / sizeof real_times [0])
/* Estimate the current sector.
The sector currently passing under the disc heads is estimated from the
current "simulation time," which is the number of event ticks since the
simulation run was started, and the simulated disc rotation time. The
computation logic is:
current_sector := (simulator_time / per_sector_time) MOD sectors_per_track;
*/
#define CURRENT_SECTOR(cvptr,uptr) \
(uint32) fmod (sim_gtime () / cvptr->dlyptr->sector_full, \
drive_props [GET_MODEL (uptr->flags)].sectors)
/* Command properties table.
The validity of each command for a given controller type is checked against
the command properties table when it is prepared for execution. The table
also includes the count of inbound or outbound parameters, the class of the
command, and flags that indicate certain common actions that are be taken.
Implementation notes:
1. The verify field of the Read_Without_Verify property record is set to
TRUE so that address verification will be done if a track boundary is
crossed. Initial verification is suppressed by setting the controller's
verify field to FALSE during initial Read_Without_Verify processing.
*/
typedef struct {
uint32 param_count; /* count of input or output parameters */
CNTLR_CLASS classification; /* command classification */
t_bool valid [CNTLR_COUNT]; /* command validity, indexed by CNTLR_TYPE */
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 */
t_bool verify_address; /* command does address verification */
t_bool idle_at_end; /* command idles the controller at completion */
uint32 preamble_size; /* size of preamble in words */
uint32 transfer_size; /* size of data transfer in words */
uint32 postamble_size; /* size of postamble in words */
} COMMAND_PROPERTIES;
typedef const COMMAND_PROPERTIES *PRPTR;
#define T TRUE
#define F FALSE
static const COMMAND_PROPERTIES cmd_props [] = {
/* parm opcode valid for clr unit unit unit seek addr end pre xfer post */
/* I/O classification MAC ICD CS80 stat fld chk acc wait verf idle size size size */
/* ---- -------------- --- --- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- */
{ 0, Class_Read, { T, T, F }, T, F, T, T, F, T, F, 15, 128, 7 }, /* 00 = Cold_Load_Read */
{ 0, Class_Control, { T, T, F }, T, T, T, T, T, F, T, 0, 0, 0 }, /* 01 = Recalibrate */
{ 2, Class_Control, { T, T, F }, T, T, T, T, F, F, T, 0, 0, 0 }, /* 02 = Seek */
{ 2, Class_Status, { T, T, F }, F, T, F, F, F, F, F, 0, 0, 0 }, /* 03 = Request_Status */
{ 1, Class_Status, { T, T, F }, T, T, T, T, F, F, F, 0, 0, 0 }, /* 04 = Request_Sector_Address */
{ 0, Class_Read, { T, T, F }, T, T, T, T, T, T, F, 15, 128, 7 }, /* 05 = Read */
{ 0, Class_Read, { T, T, F }, T, T, T, T, T, F, F, 12, 138, 0 }, /* 06 = Read_Full_Sector */
{ 1, Class_Read, { T, T, F }, T, T, T, T, T, T, F, 0, 0, 0 }, /* 07 = Verify */
{ 0, Class_Write, { T, T, F }, T, T, T, T, T, T, F, 15, 128, 7 }, /* 10 = Write */
{ 0, Class_Write, { T, T, F }, T, T, T, T, T, F, F, 12, 138, 0 }, /* 11 = Write_Full_Sector */
{ 0, Class_Control, { T, T, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 12 = Clear */
{ 0, Class_Write, { T, T, F }, T, T, T, T, T, F, F, 15, 128, 7 }, /* 13 = Initialize */
{ 2, Class_Control, { T, T, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 14 = Address_Record */
{ 7, Class_Status, { T, F, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 15 = Request_Syndrome */
{ 1, Class_Read, { T, T, F }, T, T, T, T, T, T, F, 15, 128, 7 }, /* 16 = Read_With_Offset */
{ 0, Class_Control, { T, T, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 17 = Set_File_Mask */
{ 0, Class_Invalid, { F, F, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 20 = Invalid_Opcode */
{ 0, Class_Invalid, { F, F, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 21 = Invalid_Opcode */
{ 0, Class_Read, { T, T, F }, T, T, T, T, T, T, F, 15, 128, 7 }, /* 22 = Read_Without_Verify */
{ 1, Class_Status, { T, F, F }, T, F, F, F, F, F, F, 0, 0, 0 }, /* 23 = Load_TIO_Register */
{ 2, Class_Status, { T, T, F }, F, F, F, F, F, F, F, 0, 0, 0 }, /* 24 = Request_Disc_Address */
{ 0, Class_Control, { T, T, F }, T, F, F, F, F, F, T, 0, 0, 0 }, /* 25 = End */
{ 0, Class_Control, { T, F, F }, T, T, T, F, F, F, F, 0, 0, 0 } /* 26 = Wakeup */
};
/* Command functions table.
At each phase of command execution, the controller may return zero or more
functions for the interface to perform. The functions control the transfer
of parameters and data to and from the CPU. Note that commands do not
necessarily use all available phases.
Implementation notes:
1. Commands usually return BUSY and IFGTC functions to begin. However, the
Clear and Set File Mask commands delay the IFGTC function, which requests
channel service, to the End Phase to ensure that the WRTIO function
completes before the channel program continues. This ensures that an End
I/O order will pick up the correct status value from the interface.
2. Invalid commands have WRTIO in their Idle_Phase entries because the
diagnostic expects to see Illegal_Opcode status immediately after the SIO
program ends.
*/
typedef CNTLR_IFN IFN_ARRAY [7];
static const IFN_ARRAY cmd_functions [] = { /* indexed by CNTLR_OPCODE */
/* 00 = Cold_Load_Read */
{ BUSY | SRTRY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFIN, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 01 = Recalibrate */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
WRTIO | FREE }, /* End Phase */
/* 02 = Seek */
{ BUSY | IFGTC | STDFL, /* Idle Phase */
IFOUT | STDFL, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
IFOUT | WRTIO | RQSRV | FREE }, /* End Phase */
/* 03 = Request_Status */
{ BUSY | IFGTC, /* Idle Phase */
IFIN | STDFL, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
WRTIO | FREE | RQSRV }, /* End Phase */
/* 04 = Request_Sector_Address */
{ BUSY | IFGTC, /* Idle Phase */
IFIN | STDFL, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
WRTIO | RQSRV | FREE }, /* End Phase */
/* 05 = Read */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFIN, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 06 = Read_Full_Sector */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFIN, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 07 = Verify */
{ BUSY | IFGTC | STDFL, /* Idle Phase */
IFOUT, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 10 = Write */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFOUT, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 11 = Write_Full_Sector */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFOUT, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 12 = Clear */
{ BUSY, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
IFGTC | WRTIO | STDFL | FREE }, /* End Phase */
/* 13 = Initialize */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFOUT, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 14 = Address_Record */
{ BUSY | IFGTC | STDFL, /* Idle Phase */
IFOUT | STDFL, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
IFOUT | WRTIO | RQSRV | FREE }, /* End Phase */
/* 15 = Request_Syndrome */
{ BUSY | IFGTC, /* Idle Phase */
IFIN | STDFL, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
WRTIO | RQSRV | FREE }, /* End Phase */
/* 16 = Read_With_Offset */
{ BUSY | IFGTC | STDFL, /* Idle Phase */
IFOUT, /* Parameter Phase */
RQSRV | STDFL, /* Seek Phase */
0, /* Rotate Phase */
IFIN, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 17 = Set_File_Mask */
{ BUSY | SRTRY, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
IFGTC | WRTIO | STDFL | FREE }, /* End Phase */
/* 20 = Invalid_Opcode */
{ BUSY | IFGTC | WRTIO, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
FREE }, /* End Phase */
/* 21 = Invalid_Opcode */
{ BUSY | IFGTC | WRTIO, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
FREE }, /* End Phase */
/* 22 = Read_Without_Verify */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
IFIN, /* Data Phase */
0, /* Intersector Phase */
STDFL | WRTIO | RQSRV | FREE }, /* End Phase */
/* 23 = Load_TIO_Register */
{ BUSY | IFGTC | STDFL, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
IFOUT | WRTIO | RQSRV | FREE }, /* End Phase */
/* 24 = Request_Disc_Address */
{ BUSY | IFGTC, /* Idle Phase */
IFIN | STDFL, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
WRTIO | RQSRV | FREE }, /* End Phase */
/* 25 = End */
{ IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
0 }, /* End Phase */
/* 26 = Wakeup */
{ BUSY | IFGTC, /* Idle Phase */
0, /* Parameter Phase */
0, /* Seek Phase */
0, /* Rotate Phase */
0, /* Data Phase */
0, /* Intersector Phase */
WRTIO | STDFL | FREE } /* End Phase */
};
/* Status functions table.
The End Phase functions above are proper for commands that complete normally.
Commands that return an error status return additional functions that depend
on the particular status code. An entry in this table is ORed with the End
Phase function to produce the final function set that is returned to the
interface.
*/
static const CNTLR_IFN status_functions [] = { /* indexed by CNTLR_STATUS */
0, /* 000 = Normal Completion */
STINT | WRTIO | FREE, /* 001 = Illegal Opcode */
STDFL | WRTIO | FREE, /* 002 = Unit Available */
STINT | WRTIO | FREE, /* 003 = Illegal Drive Type */
0, /* 004 = (undefined) */
0, /* 005 = (undefined) */
0, /* 006 = (undefined) */
STINT | WRTIO | FREE, /* 007 = Cylinder Miscompare */
DVEND | RQSRV | WRTIO | FREE, /* 010 = Uncorrectable Data Error */
STINT | WRTIO | FREE, /* 011 = Head-Sector Miscompare */
STINT | WRTIO | FREE, /* 012 = I/O Program Error */
DVEND | RQSRV | WRTIO | FREE, /* 013 = Sync Timeout */
STINT | WRTIO | FREE, /* 014 = End of Cylinder */
0, /* 015 = (undefined) */
DVEND | RQSRV | WRTIO | FREE, /* 016 = Data Overrun */
DVEND | RQSRV | WRTIO | FREE, /* 017 = Correctable Data Error */
STINT | WRTIO | FREE, /* 020 = Illegal Spare Access */
STINT | WRTIO | FREE, /* 021 = Defective Track */
STINT | WRTIO | FREE, /* 022 = Access Not Ready */
STINT | WRTIO | FREE, /* 023 = Status-2 Error */
0, /* 024 = (undefined) */
0, /* 025 = (undefined) */
STINT | WRTIO | FREE, /* 026 = Protected Track */
STINT | WRTIO | FREE, /* 027 = Unit Unavailable */
0, /* 030 = (undefined) */
0, /* 031 = (undefined) */
0, /* 032 = (undefined) */
0, /* 033 = (undefined) */
0, /* 034 = (undefined) */
0, /* 035 = (undefined) */
0, /* 036 = (undefined) */
STINT | WRTIO | FREE /* 037 = Drive Attention */
};
/* Controller operation names */
static const BITSET_NAME flag_names [] = { /* controller flag names, in CNTLR_FLAG order */
"CLEAR", /* 000001 */
"CMRDY", /* 000002 */
"DTRDY", /* 000004 */
"EOD", /* 000010 */
"INTOK", /* 000020 */
"OVRUN", /* 000040 */
"XFRNG" /* 000100 */
};
static const BITSET_FORMAT flag_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (flag_names, 0, lsb_first, no_alt, no_bar) };
static const BITSET_NAME function_names [] = { /* interface function names, in CNTLR_IFN order */
"BUSY", /* 000000200000 */
"DSCIF", /* 000000400000 */
"SELIF", /* 000001000000 */
"IFIN", /* 000002000000 */
"IFOUT", /* 000004000000 */
"IFGTC", /* 000010000000 */
"IFPRF", /* 000020000000 */
"RQSRV", /* 000040000000 */
"DVEND", /* 000100000000 */
"SRTRY", /* 000200000000 */
"STDFL", /* 000400000000 */
"STINT", /* 001000000000 */
"WRTIO", /* 002000000000 */
"FREE" /* 004000000000 */
};
static const BITSET_FORMAT function_format = /* names, offset, direction, alternates, bar */
{ FMT_INIT (function_names, 16, lsb_first, no_alt, no_bar) };
static const char invalid_name [] = "Invalid";
static const char *opcode_name [] = { /* command opcode names, in CNTLR_OPCODE order */
"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 */
};
#define OPCODE_LENGTH 22 /* length of the longest opcode name */
static const char *const status_name [] = { /* command status names, in CNTLR_STATUS order */
"Normal Completion", /* 000 */
"Illegal Opcode", /* 001 */
"Unit Available", /* 002 */
"Illegal Drive Type", /* 003 */
NULL, /* 004 */
NULL, /* 005 */
NULL, /* 006 */
"Cylinder Miscompare", /* 007 */
"Uncorrectable Data Error", /* 010 */
"Head-Sector Miscompare", /* 011 */
"I/O Program Error", /* 012 */
"Sync Timeout", /* 013 */
"End of Cylinder", /* 014 */
NULL, /* 015 */
"Data Overrun", /* 016 */
"Correctable Data Error", /* 017 */
"Illegal Spare Access", /* 020 */
"Defective Track", /* 021 */
"Access Not Ready", /* 022 */
"Status-2 Error", /* 023 */
NULL, /* 024 */
NULL, /* 025 */
"Protected Track", /* 026 */
"Unit Unavailable", /* 027 */
NULL, /* 030 */
NULL, /* 031 */
NULL, /* 032 */
NULL, /* 033 */
NULL, /* 034 */
NULL, /* 035 */
NULL, /* 036 */
"Drive Attention" /* 037 */
};
#define STATUS_LENGTH 24 /* length of the longest status name */
static const char *state_name [] = { /* controller state names, in CNTLR_STATE order */
"idle",
"wait",
"busy"
};
static const char *phase_name [] = { /* unit state names, in CNTLR_PHASE order */
"idle",
"parameter",
"seek",
"rotate",
"data",
"intersector",
"end"
};
/* Disc library local controller routines */
static CNTLR_IFN_IBUS start_command (CVPTR cvptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data);
static CNTLR_IFN_IBUS continue_command (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data);
static CNTLR_IFN_IBUS poll_drives (CVPTR cvptr);
static void end_command (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status);
static t_bool start_seek (CVPTR cvptr, UNIT *uptr);
static t_bool start_read (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags);
static void end_read (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags);
static t_bool start_write (CVPTR cvptr, UNIT *uptr);
static void end_write (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags);
static t_bool position_sector (CVPTR cvptr, UNIT *uptr);
static void next_sector (CVPTR cvptr, UNIT *uptr);
static void io_error (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status);
static void set_completion (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status);
static void clear_controller (CVPTR cvptr, CNTLR_CLEAR clear_type);
static void idle_controller (CVPTR cvptr);
/* Disc library local utility routines */
static void set_address (CVPTR cvptr, uint32 index);
static void wait_timer (CVPTR cvptr, FLIP_FLOP action);
static HP_WORD drive_status (UNIT *uptr);
static t_stat activate_unit (CVPTR cvptr, UNIT *uptr);
static void set_rotation (CVPTR cvptr, UNIT *uptr);
static void set_file_pos (CVPTR cvptr, UNIT *uptr, uint32 model);
/* Disc library global controller routines */
/* Disc controller interface.
This routine simulates the hardware interconnection between the disc
controller and the CPU interface. This routine is called whenever the flag
state changes. This would be when a new command is to be started, when
command parameters are supplied or status words are retrieved, and when
sector data is read or written. It must also be called when the unit service
routine is entered. The caller passes in the set of interface flags and the
contents of the data buffer. The routine returns a set of functions and, if
IFIN is included in set, the new content of the data buffer.
In hardware, the controller microcode executes in one of three 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 selected and
checked for a command; if present, it is executed. If not, the
interface is disconnected, and then 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 selected 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
interface is disconnected, and the Poll Loop continues.
2. In the Command Wait Loop, which looks for commands.
In each pass of the loop, the currently selected 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.74 second timer is running. If it
expires before a command is received, the file mask is reset, the current
interface is disconnected, and the Poll Loop is entered.
3. In command execution, which processes the current command.
While a command is executing, any waits for input parameters, seek
completion, data transfers, or output status words are handled
internally. Each wait is governed by the 1.74 second timer; if it
expires, the command is aborted, the file mask is reset, the current
interface is disconnected, and the Poll Loop is reentered.
In simulation, these states are represented by the CNTLR_STATE values
Idle_State, Wait_State, and Busy_State, respectively.
A MAC controller operates from one to eight drives, represented by an array
of one to eight UNITs. The unit number present in the command is used to
index to the target unit via the "units" pointer in the DEVICE structure.
One additional unit that represents the controller is required, separate from
the individual drive units. 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 is
also scheduled on the controller unit to limit the amount of time the
controller will wait for the interface to supply a command or parameter.
An ICD simulation manages a single unit corresponding to the drive in which
the controller is integrated. A device interface declares a UNIT array
corresponding to the number of drives supported and passes the unit number to
use during controller initialization. A controller unit is not used, as all
commands are scheduled on the drive unit associated with a given controller.
On entry, the flags and controller state are examined to determine if a new
controller command should be initiated or the current command should be
continued. If this routine is being called as a result of an event service,
"uptr" will point at the unit being serviced. Otherwise, "uptr" will be NULL
(for example, when starting a new controller command).
If the CLEARF flag is asserted, then perform a hard clear on the controller.
Otherwise, if a 3000 channel error has occurred, then terminate any command
in progress and return I/O Program Error status. Otherwise, if the
controller is currently busy with a command, or if this is an event service
entry, then process the next step of the command. Otherwise, if the CMRDY
flag is asserted, then start a new command. If none of these cases pertain,
or if the controller is now idle, poll the drives for attention.
In all cases, return a combined function set and outbound data word to the
caller.
Implementation notes:
1. This routine will be entered when the drive unit event service occurs
for seek completion, and Seek_Phase processing in continue_command will
set the drive's Attention bit. The drives must then be polled and the
return functions and TIO value set to generate a CPU interrupt.
A seek command started on one drive while a second drive already has its
Attention bit set would seem to overwrite the first drive's Seek
completion status with the second drive's Attention status. However,
this won't occur because INTOK will not be set until the first drive's
channel program completes, and so the drive poll is inhibited until then.
*/
CNTLR_IFN_IBUS dl_controller (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags, CNTLR_IBUS data)
{
CNTLR_IFN_IBUS outbound;
dpprintf (cvptr->device, DL_DEB_IOB, "Controller (%s) received data %06o with flags %s\n",
state_name [cvptr->state], data, fmt_bitset (flags, flag_format));
if (flags & CLEARF) { /* if the CLEAR flag is asserted */
clear_controller (cvptr, Hard_Clear); /* then perform a hard clear on the controller */
outbound = NO_ACTION; /* and take no other action on return */
dpprintf (cvptr->device, DL_DEB_CMD, "Hard clear\n");
}
else if (flags & XFRNG) { /* otherwise if a channel error has occurred */
end_command (cvptr, uptr, IO_Program_Error); /* then terminate the command with error status */
cvptr->spd_unit = 0; /* and clear the SPD and unit parts of the status */
outbound = status_functions [IO_Program_Error] /* set the Status-1 value for WRTIO */
| S1_STATUS (IO_Program_Error);
}
else if (uptr || cvptr->state == Busy_State) /* otherwise if a command is in process */
outbound = continue_command (cvptr, uptr, flags, data); /* then continue with command processing */
else if (flags & CMRDY) /* otherwise if a new command is ready */
outbound = start_command (cvptr, flags, data); /* then begin command execution */
else /* otherwise there's nothing to do */
outbound = NO_ACTION; /* except possibly poll for attention */
if (cvptr->state == Idle_State /* if the controller is idle */
&& cvptr->type == MAC /* and it's a MAC controller */
&& flags & INTOK) /* and interrupts are allowed */
outbound = poll_drives (cvptr); /* then poll the drives for attention */
dpprintf (cvptr->device, DL_DEB_IOB, "Controller (%s) returned data %06o with functions %s\n",
state_name [cvptr->state], DLIBUS (outbound),
fmt_bitset (DLIFN (outbound), function_format));
return outbound;
}
/* Start a new command.
This routine simulates the controller microcode entry into the command
executor corresponding to the command presented by the CPU interface. It's
called when the controller is waiting for a command and the interface asserts
CMRDY to indicate that a new command is available. It returns a set of
action functions and a data word to the caller. For a good command, it also
sets up the next phase of operation on the controller and/or drive unit and
schedules the unit(s) as appropriate.
On entry, the command word is supplied in the "inbound_data" parameter; this
simulates the microcode issuing an IFPRF (Interface Prefetch) to obtain the
command. The opcode is isolated from the command word and checked for
validity. If it's OK, it's used as an index into the command properties
table. If the command contains a unit number field, it is extracted, checked
for validity, and used to derive a pointer to the corresponding UNIT
structure. If the command does not access the drive, or if the unit number
is invalid, the unit pointer is set to NULL. A pointer to the controller
unit is also set up; for ICD controllers, the controller and drive unit are
the same.
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.
If a diagnostic override is defined, and the cylinder, head, sector, and
opcode values from the current override entry match their corresponding
controller values, then the status and SPD values are set from the entry
rather than being cleared, and the pointer is moved to the next entry. The
controller is then set to the busy state, and the validity of the opcode and
unit are checked. If any errors are detected, the appropriate status is set,
and the controller unit is scheduled for the End_Phase to simulate the
controller processing overhead.
If the command and unit are valid, then if the command accesses a drive unit,
the unit's OPCODE field is set, and any pending Attention status is cleared.
If the command takes or returns parameters, then the Parameter_Phase is set
up on the controller unit and the wait timer is started. Commands that
return parameters temporarily store their parameter values in the sector
buffer at this time for return as the CPU interface requests them. The Cold
Load Read and Recalibrate commands start their respective seeks at this time,
and commands that complete immediately, e.g., Set File Mask, Wakeup, etc.,
schedule the End_Phase on the controller unit and set up the status value for
return if they end with the WRTIO function.
Finally, the controller and/or drive units are activated if they were
scheduled. If a seek is in progress on a drive when a command that
waits for seek completion is started, the unit is not rescheduled. Instead,
the unit is left in the Seek_Phase, but the unit's OPCODE field is changed to
reflect the new command so that the command will start automatically when the
seek completes.
Implementation notes:
1. A command is started only if the controller is not busy. Therefore, the
target unit can be only in either the Seek_Phase or the Idle_Phase, as
all other phases will have the controller in the Busy_State.
2. Commands that access units and take parameters (e.g., Verify) set up the
parameter access on the controller unit but perform the rest of the
operation on the drive unit. The controller must be used so that
parameters may be read for the next command for a unit that is currently
seeking (stacked commands wait for seek completion after parameters have
been read).
3. Drive Attention may be still set on a drive that has completed a seek but
has not been able to interrupt the CPU before a new command is started.
Therefore, Attention is always cleared when a command starts.
4. In hardware, the Recalibrate command waits for a seek-in-progress to
complete before the RCL tag is sent to the drive. In simulation,
repositioning the heads to cylinder 0 is started immediately, but any
remaining time from a seek-in-progress is added to the time required for
repositioning. The effect is that recalibration completes in the time it
would have taken for the seek-in-progress to complete followed by
repositioning from that location.
5. The Cold Load Read command does not check for Access Not Ready before
issuing the seek to cylinder 0, so if a seek or recalibrate is in
progress, the drive will reject it with a Seek Check error. However, the
command continues, and when the seek in progress completes and the read
begins, the Seek Check error will abort the command at this point with a
Status-2 Error.
6. ECC is not simulated, so the Request Syndrome command always returns zero
values for the displacement and patterns and Uncorrectable Data Error for
the status. Correctable Data Error status cannot occur unless a
diagnostic override is in effect.
7. The Wakeup command references a drive unit but is scheduled on the
controller unit because it may be issued while the drive is seeking.
*/
static CNTLR_IFN_IBUS start_command (CVPTR cvptr, CNTLR_FLAG_SET inbound_flags, CNTLR_IBUS inbound_data)
{
UNIT *cuptr, *duptr, *rptr;
uint32 unit;
int32 seek_wait_time;
PRPTR props;
CNTLR_IFN_IBUS outbound;
DIAG_ENTRY *dop = NULL;
wait_timer (cvptr, CLEAR); /* stop the command wait timer */
cvptr->opcode = CM_OPCODE (inbound_data); /* get the opcode from the command */
if (cvptr->opcode > LAST_OPCODE /* if the opcode is undefined */
|| cvptr->type > LAST_CNTLR /* or the controller type is undefined */
|| cmd_props [cvptr->opcode].valid [cvptr->type] == FALSE) /* or the opcode is not valid for this controller */
cvptr->opcode = Invalid_Opcode; /* then replace it with the invalid opcode */
props = &cmd_props [cvptr->opcode]; /* get the properties associated with the opcode */
if (cvptr->type == MAC) { /* if this a MAC controller */
if (props->unit_field) /* then if the unit field is defined */
unit = CM_UNIT (inbound_data); /* then get it from the command */
else /* otherwise the unit is not specified in the command */
unit = 0; /* so the unit is always unit 0 */
cuptr = CNTLR_UPTR; /* set the controller unit pointer */
if (unit > DL_MAXDRIVE /* if the unit number is invalid */
|| props->unit_access == FALSE) /* or the command accesses the controller only */
duptr = NULL; /* then the drive pointer does not correspond to a unit */
else /* otherwise the command accesses a valid drive unit */
duptr = cvptr->device->units + unit; /* so set the drive pointer to the unit */
}
else { /* otherwise this is an ICD or CS/80 controller */
unit = 0; /* so the unit value isn't used */
cuptr = duptr = /* and the unit number was predefined */
cvptr->device->units + cvptr->poll_unit; /* when the controller structure was initialized */
}
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command started\n",
unit, opcode_name [cvptr->opcode]);
if (cvptr->dop_index >= 0) /* if the diagnostic override table is defined */
dop = cvptr->dop_base + cvptr->dop_index; /* then point at the current entry */
if (dop /* if the table entry exists */
&& dop->cylinder == cvptr->cylinder /* and the cylinder, */
&& dop->head == cvptr->head /* head, */
&& dop->sector == cvptr->sector /* sector, */
&& dop->opcode == cvptr->opcode) { /* and opcode values match the current values */
cvptr->spd_unit = dop->spd | unit; /* then override the Spare/Protected/Defective */
cvptr->status = dop->status; /* and status values from the override entry */
cvptr->dop_index++; /* point at the */
dop++; /* next table entry */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u cylinder %u head %u sector %u diagnostic override\n",
unit, cvptr->cylinder, cvptr->head, cvptr->sector);
}
else if (props->clear_status) { /* otherwise if this command clears prior status */
cvptr->status = Normal_Completion; /* then do it */
cvptr->spd_unit = unit; /* and save the unit number for status requests */
}
cvptr->state = Busy_State; /* the controller is now busy */
cvptr->index = 0; /* reset the buffer index */
cvptr->count = 0; /* and the sector/word count */
cvptr->verify = props->verify_address; /* set the address verification flag */
cuptr->OPCODE = cvptr->opcode; /* set the controller unit opcode */
cuptr->wait = NO_EVENT; /* and assume no controller scheduling */
outbound = cmd_functions [cvptr->opcode] [Idle_Phase]; /* set up the initial function set and zero data */
if (cvptr->opcode == Invalid_Opcode) /* if the opcode is invalid */
set_completion (cvptr, cuptr, Illegal_Opcode); /* then finish with an illegal opcode error */
else if (props->unit_check && unit > MAX_UNIT) /* otherwise if the unit number is checked and is illegal */
set_completion (cvptr, cuptr, Unit_Unavailable); /* then finish with a unit unavailable error */
else if (props->unit_check && unit > DL_MAXDRIVE /* otherwise if the unit number is checked and is invalid */
|| props->seek_wait && (drive_status (duptr) & S2_STOPS)) /* or if we're waiting for an offline drive */
set_completion (cvptr, cuptr, Status_2_Error); /* then finish with a Status-2 error */
else { /* otherwise the command and unit are valid */
if (duptr) { /* if the drive unit is accessed */
duptr->OPCODE = cvptr->opcode; /* then set the drive opcode for later reference */
duptr->wait = NO_EVENT; /* assume no drive scheduling */
duptr->STATUS &= ~S2_ATTENTION; /* clear any pending Attention status */
}
if (props->param_count != 0) { /* if the command takes or returns parameters */
cvptr->length = props->param_count; /* then set the parameter count */
cuptr->PHASE = Parameter_Phase; /* set up the parameter transfer on the controller */
wait_timer (cvptr, SET); /* and start the timer to wait for the first parameter */
}
switch (cvptr->opcode) { /* dispatch the command for initiation */
case Cold_Load_Read:
cvptr->cylinder = 0; /* set the cylinder address to 0 */
cvptr->head = CM_HEAD (inbound_data); /* set the head and */
cvptr->sector = CM_SECTOR (inbound_data); /* sector addresses from the command */
if (start_seek (cvptr, duptr) == FALSE) /* start the seek; if it failed */
set_completion (cvptr, cuptr, Status_2_Error); /* then set up the completion status */
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s from cylinder %u head %u sector %u\n",
unit, opcode_name [Cold_Load_Read], cvptr->cylinder, cvptr->head, cvptr->sector);
break; /* wait for seek completion */
case Recalibrate:
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s to cylinder 0\n",
unit, opcode_name [Recalibrate]);
if (duptr->PHASE == Seek_Phase) { /* if the unit is currently seeking */
seek_wait_time = sim_activate_time (duptr); /* then get the remaining event time */
sim_cancel (duptr); /* cancel the event to allow rescheduling */
duptr->PHASE = Idle_Phase; /* and idle the drive so that the seek succeeds */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command waiting for seek completion\n",
unit, opcode_name [Recalibrate]);
}
else /* otherwise the drive is idle */
seek_wait_time = 0; /* so there's no seek wait time */
if (start_seek (cvptr, duptr) == FALSE) /* start the seek; if it failed */
set_completion (cvptr, cuptr, Status_2_Error); /* then set up the completion status */
else if (cvptr->type == MAC) /* otherwise if this a MAC controller */
set_completion (cvptr, cuptr, Normal_Completion); /* then schedule seek completion */
duptr->wait = duptr->wait + seek_wait_time; /* increase the delay by any remaining seek time */
break; /* and wait for the recalibrate to complete */
case Request_Status:
cvptr->buffer [0] = (DL_BUFFER) (cvptr->spd_unit /* set the Status-1 value */
| S1_STATUS (cvptr->status)); /* into the buffer */
if (cvptr->type == MAC) /* if this a MAC controller */
if (unit > DL_MAXDRIVE) /* then if the unit number is invalid */
rptr = NULL; /* then it does not correspond to a unit */
else /* otherwise the unit is valid */
rptr = cvptr->device->units + unit; /* so get the address of the referenced unit */
else /* otherwise it is not a MAC controller */
rptr = duptr; /* so the referenced unit is the current unit */
cvptr->buffer [1] = (DL_BUFFER) drive_status (rptr); /* set the Status-2 value into the buffer */
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s returns %sunit %u | %s and %s%s | %s\n",
unit, opcode_name [Request_Status],
fmt_bitset (cvptr->spd_unit, status_1_format),
CM_UNIT (cvptr->spd_unit), dl_status_name (cvptr->status),
(cvptr->buffer [1] & S2_ERROR ? "error | " : ""),
drive_props [S2_TO_DRIVE_TYPE (cvptr->buffer [1])].name,
fmt_bitset (cvptr->buffer [1], status_2_format));
if (rptr) /* if the referenced unit is valid */
rptr->STATUS &= ~S2_FIRST_STATUS; /* then clear the First Status bit */
cvptr->spd_unit = S1_UNIT (unit); /* save the unit number referenced in the command */
if (unit > MAX_UNIT) /* if the unit number is illegal */
cvptr->status = Unit_Unavailable; /* then the next status will be Unit Unavailable */
else /* otherwise a legal unit */
cvptr->status = Normal_Completion; /* clears the controller status */
break;
case Request_Sector_Address:
if (drive_status (duptr) & S2_NOT_READY) /* if the drive is not ready */
set_completion (cvptr, cuptr, Status_2_Error); /* then finish with Not Ready status */
else /* otherwise the drive is ready */
cvptr->buffer [0] = /* so calculate the current sector address */
(DL_BUFFER) CURRENT_SECTOR (cvptr, duptr);
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s returns sector %u\n",
unit, opcode_name [Request_Sector_Address], cvptr->buffer [0]);
break;
case Clear:
clear_controller (cvptr, Soft_Clear); /* clear the controller */
set_completion (cvptr, cuptr, Normal_Completion); /* and schedule the command completion */
dpprintf (cvptr->device, DL_DEB_CMD, "%s\n", opcode_name [Clear]);
break;
case Request_Syndrome:
if (cvptr->status == Correctable_Data_Error) { /* if this is a correction override */
cvptr->buffer [3] = (DL_BUFFER) dop->spd; /* then load the displacement */
cvptr->buffer [4] = (DL_BUFFER) dop->cylinder; /* and three */
cvptr->buffer [5] = (DL_BUFFER) dop->head; /* syndrome words */
cvptr->buffer [6] = (DL_BUFFER) dop->sector; /* from the override entry */
cvptr->dop_index++; /* point at the */
dop++; /* next table entry */
}
else { /* otherwise no correction data was supplied */
cvptr->buffer [3] = 0; /* so the displacement is always zero */
cvptr->buffer [4] = 0; /* as are */
cvptr->buffer [5] = 0; /* the three */
cvptr->buffer [6] = 0; /* syndrome words */
if (cvptr->status == Normal_Completion) /* if we've been called without an override */
cvptr->status = Uncorrectable_Data_Error; /* then presume that an uncorrectable error occurred */
}
cvptr->buffer [0] = (DL_BUFFER) (cvptr->spd_unit /* save the Status-1 value */
| S1_STATUS (cvptr->status)); /* in the buffer */
set_address (cvptr, 1); /* save the CHS values in the buffer */
dpprintf (cvptr->device, DL_DEB_CMD, "%s returns %sunit %u | %s | cylinder %u head %u sector %u | "
"syndrome %06o %06o %06o %06o\n",
opcode_name [Request_Syndrome], fmt_bitset (cvptr->spd_unit, status_1_format),
CM_UNIT (cvptr->spd_unit), dl_status_name (cvptr->status),
cvptr->cylinder, cvptr->head, cvptr->sector,
cvptr->buffer [3], cvptr->buffer [4], cvptr->buffer [5], cvptr->buffer [6]);
next_sector (cvptr, cvptr->device->units /* address the next sector of the last unit used */
+ S1_UNIT (cvptr->spd_unit));
break;
case Set_File_Mask:
cvptr->file_mask = CM_FILE_MASK (inbound_data); /* save the supplied file mask */
outbound |= CM_RETRY (inbound_data); /* return the retry count */
set_completion (cvptr, cuptr, Normal_Completion); /* schedule the command completion */
dpprintf (cvptr->device, DL_DEB_CMD, "%s to %sretries %u\n",
opcode_name [Set_File_Mask], fmt_bitset (cvptr->file_mask, file_mask_format),
CM_RETRY (inbound_data));
break;
case Request_Disc_Address:
set_address (cvptr, 0); /* set the controller's CHS values into the buffer */
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s returns cylinder %u head %u sector %u\n",
unit, opcode_name [Request_Disc_Address], cvptr->cylinder, cvptr->head, cvptr->sector);
break;
case End:
dpprintf (cvptr->device, DL_DEB_CMD, "%s\n", opcode_name [End]);
end_command (cvptr, NULL, Normal_Completion); /* end the command and idle the controller */
break;
case Wakeup:
set_completion (cvptr, cuptr, Unit_Available); /* schedule the command completion */
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s\n",
unit, opcode_name [Wakeup]);
break;
/* these commands wait for seek completion before starting */
case Read_Without_Verify:
cvptr->verify = FALSE; /* do not verify until a track is crossed */
inbound_data &= ~CM_SPD_MASK; /* clear the SPD bits to avoid changing the state */
/* fall into the Initialize case */
case Initialize:
cvptr->spd_unit |= CM_SPD (inbound_data); /* merge the SPD flags with the unit */
/* fall into the read/write cases */
case Read:
case Read_Full_Sector:
case Write:
case Write_Full_Sector:
if (duptr->PHASE == Seek_Phase) /* if the unit is currently seeking */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command waiting for seek completion\n",
unit, opcode_name [cvptr->opcode]);
else /* otherwise the unit is idle */
set_rotation (cvptr, duptr); /* so set up the rotation phase and latency */
break;
/* these commands take parameters but otherwise require no preliminary work */
case Seek:
case Verify:
case Address_Record:
case Read_With_Offset:
case Load_TIO_Register:
break;
case Invalid_Opcode: /* for completeness; invalid commands are not dispatched */
break;
}
}
if (cvptr->state == Busy_State) { /* if the command has not completed immediately */
if (cuptr->wait != NO_EVENT && cvptr->type == MAC) /* then if the controller unit is scheduled */
activate_unit (cvptr, cuptr); /* then activate it */
if (duptr && duptr->wait != NO_EVENT) /* and if the drive unit is valid and scheduled */
activate_unit (cvptr, duptr); /* then activate it as well */
}
if (outbound & WRTIO) /* if status is expected immediately */
outbound |= S1_STATUS (cvptr->status) | cvptr->spd_unit; /* then return the Status-1 value */
return outbound; /* return the data word and function set */
}
/* Continue the current command.
This routine simulates continuing execution of the controller microcode for
the current command. It's called whenever the controller has had to wait for
action from the CPU interface or the drive unit, and that action has now
occurred. Typically, this would be whenever the interface flag status
changes, or a unit's event service has been entered. It returns a set of
action functions and a data word to the caller. It also sets up the next
phase of operation on the controller and/or drive unit and schedules the
unit(s) as appropriate.
On entry, the "uptr" parameter is set to NULL if the controller was called
for a CPU interface action, or it points to the unit whose event service was
just called; this may be either the controller unit or a drive unit. The
"inbound_flags" and "inbound_data" parameters contain the CPU interface flags
and data buffer values.
If the entry is for a unit service, the unit number of the drive requesting
service is determined. If the entry is for the CPU interface, the controller
is checked; if it's idle, the routine returns because no command is in
progress.
While a unit is activated, the current phase indicates the reason for the
activation, i.e., what the simulated drive is "doing" at the moment, as
follows:
Idle_Phase -- waiting for the next command to be issued (note that
this phase, which indicates that the unit is not
scheduled, is distinct from the controller Idle_State,
which indicates that the controller itself is idle)
Parameter_Phase -- waiting for a parameter to be transferred to or from
the interface
Seek_Phase -- waiting for a seek to complete (explicit or automatic)
Rotate_Phase -- waiting for the target sector to arrive under the head
Data_Phase -- waiting for the interface to accept or return the next
data word to be transferred to or from the drive
Intersector_Phase -- waiting for the controller to finish executing the
end-of-sector microcode
End_Phase -- waiting for the controller to finish executing the
microcode corresponding to the current command
Depending on the current command opcode and phase, a number of actions may be
taken:
Idle_Phase -- If the controller unit is being serviced, then the 1.74
second command wait timer has expired while waiting for a new command.
Reset the file mask and idle the controller.
Parameter_Phase -- If the controller unit is being serviced, then the 1.74
second command wait timer has expired while waiting for a parameter from
the CPU. Reset the file mask and idle the controller. Otherwise, for
outbound parameters, return the next word from the sector buffer and
restart the timer; if the last word has been sent, end the command. For
inbound parameters, store the next word in the appropriate controller state
variable; if the last word has been received, end the command or set up the
next operation phase (seek, rotate, etc.).
Seek_Phase -- If a Seek or Recalibrate has completed, set Drive Attention
status. All other commands have been waiting for seek completion before
starting, so set up the rotate phase to begin the command.
Rotate_Phase -- Set up the read or write of the current sector. For all
commands except Verify, proceed to the data phase to begin the data
transfer. For Verify, skip the data phase and proceed directly to the
end-of-sector processing, but schedule that event as though the full sector
rotation time had elapsed.
Data_Phase -- For read transfers, return the next word from the sector
buffer, or for write transfers, store the next word into the sector buffer,
and schedule the next data phase transfer if the CPU has not indicated an
end-of-data condition. If it has, or if the last word of the sector has
been transmitted, schedule the intersector phase.
Intersector_Phase -- Complete the read or write of the current sector. If
the CPU has indicated an end-of-data condition, end the command.
Otherwise, address the next sector and schedule the rotate phase.
End_Phase -- End the command. The end phase is used to provide a
controller delay when an operation has no other command phases.
At the completion of the current phase, the next phase is scheduled, if
required, before returning the appropriate function set and data word to the
caller.
The commands employ the various phases as follows:
Inter
Command Param Seek Rotate Data sector End
+------------------------+-------+------+--------+------+--------+-----+
| Cold Load Read | - | D | D | D | D | - |
| Recalibrate | - | D | - | - | - | - |
| Seek | c | D | - | - | - | - |
| Request Status | c | - | - | - | - | - |
| Request Sector Address | c | - | - | - | - | - |
| Read | - | - | D | D | D | - |
| Read Full Sector | - | - | D | D | D | - |
| Verify | c | - | D | - | D | - |
| Write | - | - | D | D | D | - |
| Write Full Sector | - | - | D | D | D | - |
| Clear | - | - | - | - | - | C |
| Initialize | - | - | D | D | D | - |
| Address Record | c | - | - | - | - | - |
| Request Syndrome | c | - | - | - | - | - |
| Read With Offset | c | - | D | D | D | - |
| Set File Mask | - | - | - | - | - | C |
| Invalid Opcode | - | - | - | - | - | C |
| Read Without Verify | - | - | D | D | D | - |
| Load TIO Register | c | - | - | - | - | - |
| Request Disc Address | c | - | - | - | - | - |
| End | - | - | - | - | - | - |
| Wakeup | - | - | - | - | - | C |
+------------------------+-------+------+--------+------+--------+-----+
Key:
C = controller unit is scheduled
c = controller is called directly by the CPU interface
D = drive unit is scheduled
- = the phase is not used
Implementation notes:
1. The "%.0u" print specification in the trace call absorbs the zero "unit"
value parameter without printing when the controller unit is specified.
2. The Seek command does not check for Access Not Ready before issuing the
seek, so if a prior seek is in progress, the drive will reject it with a
Seek Check error. However, 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.
3. The set of interface functions to assert on command completion is
normally specified by the End_Phase entry in the cmd_functions table,
regardless of whether or not a command has an end phase. However, the
Request Syndrome command returns either Correctable Data Error or
Uncorrectable Data Error for normal command completion. These status
returns would normally include DVEND to indicate that the command should
be retried, but that's wrong for Request Syndrome, so we explicitly
override the function set for this command.
4. Command completion must be detected by the controller state changing to
"not busy" rather than simply being "not busy" at the end of the routine.
Otherwise, a seek that completes while the controller is waiting for a
command would re-issue the end phase functions.
5. The disc is a synchronous device, so overrun or underrun can occur if the
interface is not ready when the controller must transfer data. There are
four conditions that lead to an overrun or underrun:
a. The controller is ready with a disc read word (IFCLK * IFIN), but
the interface buffer is full (DTRDY).
b. The controller needs a disc write word (IFCLK * IFOUT), but the
interface buffer is empty (~DTRDY).
c. The CPU attempts to read a word, but the interface buffer is empty
(~DTRDY).
d. The CPU attempts to write a word, but the interface buffer is full
(DTRDY).
The 13037 controller ORs the interface-supplied OVRUN signal with an
internal overrun latch that sets on condition 2 above (write underrun).
However, both the 13175A HP 1000 interface and the 30229B HP 3000
interface assert OVRUN for all four conditions, so the latch is not
simulated.
6. Not all changes of CPU interface flag status are significant. If the
routine is called when it isn't needed, the routine simply returns with
no action.
*/
static CNTLR_IFN_IBUS continue_command (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET inbound_flags, CNTLR_IBUS inbound_data)
{
const t_bool service_entry = (uptr != NULL); /* set TRUE if entered via unit service */
CNTLR_OPCODE opcode;
CNTLR_PHASE phase;
CNTLR_IFN_IBUS outbound;
t_bool controller_service, controller_was_busy;
int32 unit;
uint32 sector_count;
if (service_entry) { /* if this is an event service entry */
unit = (int32) (uptr - cvptr->device->units); /* then get the unit number */
controller_service = (uptr == CNTLR_UPTR /* set TRUE if the controller is being serviced */
&& cvptr->type == MAC);
}
else if (CNTLR_UPTR->PHASE == Idle_Phase) /* otherwise if this interface entry isn't needed */
return NO_ACTION; /* then quit as there's nothing to do */
else { /* otherwise the controller is expecting the entry */
uptr = CNTLR_UPTR; /* set up to use the controller unit */
unit = CNTLR_UNIT; /* and unit number */
controller_service = FALSE; /* but note that this isn't a service entry */
}
opcode = (CNTLR_OPCODE) uptr->OPCODE; /* get the current opcode */
phase = (CNTLR_PHASE) uptr->PHASE; /* and command phase */
if (controller_service == FALSE || phase == End_Phase)
dpprintf (cvptr->device, DL_DEB_STATE, (unit == CNTLR_UNIT
? "Controller unit%.0d %s %s phase entered from %s\n"
: "Unit %d %s %s phase entered from %s\n"),
(unit == CNTLR_UNIT ? 0 : unit), opcode_name [opcode], phase_name [phase],
(service_entry ? "service" : "interface"));
controller_was_busy = (cvptr->state == Busy_State); /* set TRUE if the controller was busy on entry */
outbound = cmd_functions [opcode] [phase]; /* set up the initial function return set */
switch (phase) { /* dispatch the phase */
case Idle_Phase: /* the command wait timer has expired */
clear_controller (cvptr, Timeout_Clear); /* so idle the controller and clear the file mask */
outbound = NO_FUNCTIONS; /* clear the function set for an idle return */
dpprintf (cvptr->device, DL_DEB_INCO, "Controller command wait timed out\n");
break;
case Parameter_Phase:
if (controller_service) { /* if the parameter wait timer has expired */
clear_controller (cvptr, Timeout_Clear); /* then idle the controller and clear the file mask */
outbound = NO_FUNCTIONS; /* clear the function set for an idle return */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command aborted with parameter wait timeout\n",
CM_UNIT (cvptr->spd_unit), opcode_name [opcode]);
}
else switch (opcode) { /* otherwise dispatch the command */
case Request_Status: /* these commands */
case Request_Disc_Address: /* return parameters */
case Request_Sector_Address: /* to the interface */
case Request_Syndrome:
if (cvptr->length == 0) /* if the last parameter has been sent */
end_command (cvptr, uptr, cvptr->status); /* then terminate the command with the preset status */
else { /* otherwise there are more to send */
outbound |= cvptr->buffer [cvptr->index++]; /* so return the next value from the buffer */
cvptr->length = cvptr->length - 1; /* and drop the parameter count */
wait_timer (cvptr, SET); /* restart the parameter timer */
}
break;
case Seek: /* these commands receive parameters */
case Address_Record: /* from the interface */
cvptr->buffer [cvptr->index++] = /* save the current one in the buffer */
(DL_BUFFER) inbound_data;
cvptr->length = cvptr->length - 1; /* and drop the parameter count */
if (cvptr->length > 0) /* if another parameter is expected */
wait_timer (cvptr, SET); /* then restart the parameter timer */
else { /* otherwise all parameters are in */
cvptr->cylinder = cvptr->buffer [0]; /* so fill in the supplied cylinder */
cvptr->head = PI_HEAD (cvptr->buffer [1]); /* and head */
cvptr->sector = PI_SECTOR (cvptr->buffer [1]); /* and sector addresses */
if (opcode == Address_Record) { /* if this is an Address Record command */
cvptr->eoc = CLEAR; /* then clear the end-of-cylinder flag */
dpprintf (cvptr->device, DL_DEB_CMD, "%s to cylinder %u head %u sector %u\n",
opcode_name [Address_Record],
cvptr->cylinder, cvptr->head, cvptr->sector);
end_command (cvptr, uptr, /* the command is now complete */
Normal_Completion);
}
else { /* otherwise it's a Seek command */
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s to cylinder %u head %u sector %u\n",
CM_UNIT (cvptr->spd_unit), opcode_name [Seek],
cvptr->cylinder, cvptr->head, cvptr->sector);
uptr = cvptr->device->units /* get the target unit */
+ CM_UNIT (cvptr->spd_unit);
if (start_seek (cvptr, uptr) == FALSE) /* start the seek; if it failed, */
end_command (cvptr, uptr, /* then report the error */
Status_2_Error);
else if (cvptr->type == MAC) /* otherwise if this a MAC controller */
end_command (cvptr, uptr, /* then complete the command and idle the controller */
Normal_Completion);
} /* otherwise an ICD command ends when the seek completes */
}
break;
case Verify:
if (inbound_data == 0) /* if the sector count is zero */
sector_count = 65536; /* then use the rollover count */
else /* otherwise */
sector_count = inbound_data; /* use the count as is */
cvptr->count = sector_count * WORDS_PER_SECTOR; /* convert to the number of words to verify */
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s %u sector%s\n",
CM_UNIT (cvptr->spd_unit), opcode_name [Verify],
sector_count, (sector_count == 1 ? "" : "s"));
wait_timer (cvptr, CLEAR); /* stop the parameter timer */
uptr = cvptr->device->units /* get the target unit */
+ CM_UNIT (cvptr->spd_unit);
if (uptr->PHASE == Seek_Phase) { /* if a seek is in progress, */
uptr->wait = NO_EVENT; /* then wait for it to complete */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command waiting for seek completion\n",
CM_UNIT (cvptr->spd_unit), opcode_name [Verify]);
}
else /* otherwise the unit is idle */
set_rotation (cvptr, uptr); /* so set up the rotation phase and latency */
break;
case Read_With_Offset:
dpprintf (cvptr->device, DL_DEB_CMD, "Unit %u %s using %soffset %+d\n",
CM_UNIT (cvptr->spd_unit), opcode_name [Read_With_Offset],
fmt_bitset (inbound_data, offset_format),
(inbound_data & PI_NEG_OFFSET ? - (int) PI_OFFSET (inbound_data)
: (int) PI_OFFSET (inbound_data)));
wait_timer (cvptr, CLEAR); /* stop the parameter timer */
uptr = cvptr->device->units /* get the target unit */
+ CM_UNIT (cvptr->spd_unit);
if (uptr->PHASE == Seek_Phase) { /* if a seek is in progress, */
uptr->wait = NO_EVENT; /* then wait for it to complete */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command waiting for seek completion\n",
CM_UNIT (cvptr->spd_unit), opcode_name [Read_With_Offset]);
}
else { /* otherwise the unit is idle */
uptr->PHASE = Seek_Phase; /* so schedule the seek phase */
uptr->wait = cvptr->dlyptr->seek_one; /* with the offset positioning delay */
}
break;
case Load_TIO_Register:
wait_timer (cvptr, CLEAR); /* stop the parameter timer */
dpprintf (cvptr->device, DL_DEB_CMD, "%s with %06o\n",
opcode_name [Load_TIO_Register], inbound_data);
end_command (cvptr, uptr, Normal_Completion); /* complete the command */
return inbound_data /* return the supplied TIO value */
| cmd_functions [Load_TIO_Register] [End_Phase];
break;
default: /* the remaining commands */
break; /* do not have a parameter phase */
}
break;
case Seek_Phase:
switch (opcode) { /* dispatch the command */
case Recalibrate:
case Seek:
if (cvptr->type == MAC) { /* if this a MAC controller */
uptr->STATUS |= S2_ATTENTION; /* set Attention in the unit status */
uptr->PHASE = Idle_Phase; /* and idle the drive */
}
else /* otherwise this is an ICD or CS/80 drive */
end_command (cvptr, uptr, Drive_Attention); /* so seeks end with Drive Attention status */
break;
case Cold_Load_Read:
cvptr->file_mask = CM_SPARE_EN; /* enable sparing in surface mode without auto-seek */
/* fall into the default case */
default: /* a command was waiting on seek completion */
set_rotation (cvptr, uptr); /* so set up the rotation phase and latency */
break;
}
break;
case Rotate_Phase:
switch (opcode) { /* dispatch the command */
case Write:
case Write_Full_Sector:
case Initialize:
start_write (cvptr, uptr); /* start the sector write */
break;
case Read:
case Read_Full_Sector:
case Read_With_Offset:
case Read_Without_Verify:
case Cold_Load_Read:
start_read (cvptr, uptr, inbound_flags); /* start the sector read */
break;
case Verify:
inbound_flags &= ~EOD; /* EOD is not relevant for Verify */
if (start_read (cvptr, uptr, inbound_flags)) { /* if the sector read was set up successfully */
uptr->PHASE = Intersector_Phase; /* then skip the data phase */
uptr->wait = cvptr->dlyptr->sector_full; /* and reschedule for the sector read time */
}
break;
default: /* the remaining commands */
break; /* do not have a rotate phase */
}
break;
case Data_Phase:
if (inbound_flags & EOD) /* if the transfer has ended */
outbound = NO_FUNCTIONS; /* then don't assert IFIN/IFOUT on return */
switch (opcode) { /* dispatch the command */
case Read:
case Read_With_Offset:
case Read_Without_Verify:
case Read_Full_Sector:
case Cold_Load_Read:
if ((inbound_flags & EOD) == NO_FLAGS) { /* if the transfer continues */
outbound |= cvptr->buffer [cvptr->index++]; /* then get the next word from the buffer */
cvptr->count = cvptr->count + 1; /* count the */
cvptr->length = cvptr->length - 1; /* transfer */
dpprintf (cvptr->device, DL_DEB_XFER, "Unit %d %s word %u is %06o\n",
unit, opcode_name [opcode],
cvptr->count, DLIBUS (outbound));
}
uptr->wait = cvptr->dlyptr->data_xfer; /* set the transfer delay */
if (cvptr->length == 0 || inbound_flags & EOD) { /* if the buffer is empty or the transfer is done */
uptr->PHASE = Intersector_Phase; /* then set up the intersector phase */
if (cvptr->device->flags & DEV_REALTIME) /* if we're in realistic timing mode */
uptr->wait = uptr->wait /* then account for the actual delay */
* (cvptr->length + cmd_props [opcode].postamble_size);
}
break;
case Write:
case Write_Full_Sector:
case Initialize:
if ((inbound_flags & EOD) == NO_FLAGS) { /* if the transfer continues */
cvptr->buffer [cvptr->index++] = /* then store the next word in the buffer */
(DL_BUFFER) inbound_data;
cvptr->count = cvptr->count + 1; /* count the */
cvptr->length = cvptr->length - 1; /* transfer */
dpprintf (cvptr->device, DL_DEB_XFER, "Unit %d %s word %u is %06o\n",
unit, opcode_name [opcode],
cvptr->count, inbound_data);
}
uptr->wait = cvptr->dlyptr->data_xfer; /* set the transfer delay */
if (cvptr->length == 0 || inbound_flags & EOD) { /* if the buffer is empty or the transfer is done */
uptr->PHASE = Intersector_Phase; /* then set up the intersector phase */
if (cvptr->device->flags & DEV_REALTIME) /* if we're in realistic timing mode */
uptr->wait = uptr->wait /* then account for the actual delay */
* (cvptr->length + cmd_props [opcode].postamble_size);
}
break;
default: /* the remaining commands */
break; /* do not have a data phase */
}
break;
case Intersector_Phase:
switch (opcode) { /* dispatch the command */
case Read:
case Read_With_Offset:
case Read_Without_Verify:
case Read_Full_Sector:
case Cold_Load_Read:
end_read (cvptr, uptr, inbound_flags); /* end the sector read */
break;
case Write:
case Write_Full_Sector:
case Initialize:
end_write (cvptr, uptr, inbound_flags); /* end the sector write */
break;
case Verify:
cvptr->count = cvptr->count - WORDS_PER_SECTOR; /* decrement the word count */
if (cvptr->count > 0) /* if there more sectors to verify */
inbound_flags &= ~EOD; /* then this is not the end of data */
else /* otherwise the command is complete */
inbound_flags |= EOD; /* and this is the end of data */
end_read (cvptr, uptr, inbound_flags); /* end the sector read */
break;
default: /* the remaining commands */
break; /* do not have an intersector phase */
}
break;
case End_Phase:
end_command (cvptr, uptr, cvptr->status); /* complete the command with the preset status */
break;
}
if (uptr->wait != NO_EVENT) /* if the unit has been scheduled */
activate_unit (cvptr, uptr); /* then activate it */
if (controller_was_busy && cvptr->state != Busy_State) { /* if the command has just completed */
if (cvptr->status == Normal_Completion /* then if the command completed normally */
|| opcode == Request_Syndrome) /* or it was a Request Syndrome command */
outbound = cmd_functions [opcode] [End_Phase]; /* then use the normal exit function set */
else /* otherwise */
outbound = status_functions [cvptr->status]; /* the function set depends on the status */
if (outbound & WRTIO) /* if the TIO register will be written */
outbound |= S1_STATUS (cvptr->status) | cvptr->spd_unit; /* then include the Status-1 value */
}
return outbound; /* return the data word and function set */
}
/* Poll the drives for Attention status.
MAC controllers complete their Seek and Recalibrate commands when the seeks
are initiated, so that other drives may be serviced during the waits. A
drive will set its Attention status when its seek completes, and the
controller must poll the drives for attention requests when it is idle and
interrupts are allowed by the CPU interface.
Starting with the last unit that had previously requested 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 a function set that
indicates 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 an empty function set
to indicate that no interrupt should be generated.
ICD controllers do not call this routine, because each controller waits for
seek completion on its dedicated drive before completing the associated Seek
or Recalibrate command.
*/
static CNTLR_IFN_IBUS poll_drives (CVPTR cvptr)
{
uint32 unit;
UNIT *units = cvptr->device->units;
dpprintf (cvptr->device, DL_DEB_INCO, "Controller polled drives for attention\n");
for (unit = 0; unit <= DL_MAXDRIVE; unit++) { /* check each unit in turn */
cvptr->poll_unit = /* start with the last unit checked */
(cvptr->poll_unit + 1) % (DL_MAXDRIVE + 1); /* and cycle back to unit 0 */
if (units [cvptr->poll_unit].STATUS & S2_ATTENTION) { /* if the unit is requesting attention, */
units [cvptr->poll_unit].STATUS &= ~S2_ATTENTION; /* clear the Attention status */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u requested attention\n",
cvptr->poll_unit);
cvptr->spd_unit = cvptr->poll_unit; /* set the controller's unit number */
cvptr->status = Drive_Attention; /* and status */
cvptr->state = Wait_State; /* set the controller state to waiting */
wait_timer (cvptr, SET); /* start the command wait timer */
return status_functions [Drive_Attention] /* tell the caller to interrupt */
| S1_STATUS (cvptr->status) | cvptr->spd_unit; /* and include the Status-1 value */
}
}
return NO_ACTION; /* no drives have attention set */
}
/* Clear the controller.
A "hard", "timeout", or "soft" clear is performed on the indicated controller
as specified by the "clear_type" parameter.
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 jumper is installed in the enabled position (which is the usual case).
The 30229B interface for the HP 3000 asserts CLEAR in response to an IORESET
signal or a programmed Master Reset if the PRESET DISABLE jumper is installed
in the enabled position.
The third condition, a "timeout clear", also causes a firmware restart but
with the PWRON flag clear. The last condition, a "soft clear" 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)
- clear the clock offset
- clear the file mask
- issue a Controller Preset to clear all connected drives
- 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
On a 7905 or 7906 drive, clearing the head register will change Read-Only
status to reflect the position of the PROTECT LOWER DISC switch.
In simulation, a hard clear occurs when an SCP RESET command is entered, or a
programmed CLC 0 instruction or Master Clear is executed. A timeout clear
occurs when the command or parameter wait timer expires. A soft clear occurs
when a programmed Clear command is issued.
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 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. If a drive unit is disabled, it is still cleared. A unit cannot be
disabled while it is active, so it must be in the idle state while
disabled. Clearing the status keeps it consistent in case it is
reenabled later.
5. A soft clear does not abort commands in progress on a drive unit.
However, a soft clear is a result of a programmed command that can only
be issued when the prior command has completed (except for Seek and
Recalibrate commands, which are never aborted).
6. In simulation, a Controller Preset only resets the specified status bits,
as the remainder of the hardware actions are not implemented.
*/
static void clear_controller (CVPTR cvptr, CNTLR_CLEAR clear_type)
{
uint32 unit_count;
UNIT *uptr;
if (clear_type == Timeout_Clear) { /* if this is a timeout clear */
cvptr->file_mask = 0; /* then clear the file mask */
idle_controller (cvptr); /* and idle the controller */
}
else { /* otherwise */
if (clear_type == Hard_Clear) { /* if this a hard clear */
cvptr->file_mask = 0; /* then clear the file mask */
if (cvptr->type == MAC) /* if this is a MAC controller */
cvptr->poll_unit = 0; /* then clear the last unit polled too */
idle_controller (cvptr); /* idle the controller */
}
else /* otherwise it's a soft clear */
cvptr->status = Normal_Completion; /* so clear the status explicitly */
if (cvptr->type == MAC) { /* if this a MAC controller */
uptr = cvptr->device->units; /* then preset all units */
unit_count = cvptr->device->numunits - 1; /* except the controller unit */
}
else { /* otherwise preset */
uptr = cvptr->device->units + cvptr->poll_unit; /* only the single unit */
unit_count = 1; /* dedicated to the controller */
}
while (unit_count > 0) { /* preset each drive in turn */
if (uptr->PHASE != Idle_Phase /* if the unit is active */
&& uptr->OPCODE != Seek /* but not seeking */
&& uptr->OPCODE != Recalibrate) { /* or recalibrating */
sim_cancel (uptr); /* then cancel the unit event */
uptr->PHASE = Idle_Phase; /* and idle it */
}
uptr->STATUS &= ~(S2_CPS | S2_READ_ONLY); /* do a "Controller Preset" on the unit */
if (uptr->flags & UNIT_PROT_U) /* if the (upper) heads are protected */
uptr->STATUS |= S2_READ_ONLY; /* then set read-only status */
uptr++; /* point at the next unit */
unit_count = unit_count - 1; /* and count the unit just cleared */
}
}
return;
}
/* Disc library global utility routines */
/* Return the name of an opcode.
A string representing the supplied controller opcode is returned to the
caller. If the opcode is 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_CNTLR /* 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 /* otherwise the type or opcode is illegal */
return invalid_name; /* so return an error indication */
}
/* Return the name of a command result status.
A string representing the supplied command result status is returned to the
caller. If the status is illegal or undefined, the string "Invalid" is
returned.
*/
const char *dl_status_name (CNTLR_STATUS status)
{
if (status <= Drive_Attention && status_name [status]) /* if the status is legal */
return status_name [status]; /* then return the status name */
else /* otherwise the status is illegal */
return invalid_name; /* so return an error indication */
}
/* Disc library global SCP support 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.
Implementation notes:
1. The pointer to the appropriate event delay times is set in case we are
being called during a RESTORE command (the assignment is redundant
otherwise).
*/
t_stat dl_attach (CVPTR cvptr, UNIT *uptr, CONST char *cptr)
{
t_stat result;
result = attach_unit (uptr, cptr); /* attach the unit */
if (result == SCPE_OK) /* if the attach succeeded */
result = dl_load_unload (cvptr, uptr, TRUE); /* then load the heads */
dl_set_timing (cvptr->device->units, /* reestablish */
(cvptr->device->flags & DEV_REALTIME), /* the delay times */
NULL, (void *) cvptr); /* pointer(s) */
return result; /* return the command result status */
}
/* 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)
{
t_stat unload, detach;
unload = dl_load_unload (cvptr, uptr, FALSE); /* unload the heads */
if (unload == SCPE_OK || unload == SCPE_INCOMP) { /* if the unload succeeded */
detach = detach_unit (uptr); /* then detach the unit */
if (detach == SCPE_OK) /* if the detach succeeded as well */
return unload; /* then return the unload status */
else /* otherwise */
return detach; /* return the detach failure status */
}
else /* otherwise the unload failed */
return unload; /* so return the failure status */
}
/* 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 both cases, the drive reports Attention
status to the controller. A MAC controller will clear a drive's Attention
status and will interrupt the CPU when the drives are polled in the Idle
Loop. An ICD controller also clears the drive's Attention status but will
assert a Parallel Poll Response only when the heads unload.
In simulation, the unit must be attached, corresponding to having a disc pack
installed in the drive, before the heads may be unloaded or loaded. If it
isn't, the routine returns SCPE_UNATT. Otherwise, the UNIT_UNLOAD flag and
the drive status are set accordingly.
If the (MAC) controller is idle, the routine returns SCPE_INCOMP to indicate
that the caller must then call the controller to poll for drive attention to
complete the command. Otherwise, it returns SCPE_OK, and the drives will be
polled automatically when the current command or command wait completes and
the controller is idled.
Implementation notes:
1. Loading or unloading the heads clears Fault and Seek Check status.
2. If we are called during a RESTORE command, the unit's flags are not
changed to avoid upsetting the state that was SAVEd.
3. Unloading an active unit does not cancel the event. This ensures that
the controller will not hang during a transfer but instead will see the
unloaded unit and fail the transfer with Access_Not_Ready status.
*/
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; /* so return "Unit not attached" if it is not */
else if ((sim_switches & SIM_SW_REST) == 0) { /* if we are not being called during a RESTORE command */
uptr->STATUS &= ~S2_CPS; /* then do a "Controller Preset" on the unit */
if (load) { /* if we are loading the heads */
uptr->flags &= ~UNIT_UNLOAD; /* then clear the unload flag */
uptr->STATUS |= S2_FIRST_STATUS; /* and set First Status */
if (cvptr->type != ICD) /* if this is not an ICD controller */
uptr->STATUS |= S2_ATTENTION; /* then set Attention status also */
}
else { /* otherwise we are unloading the heads */
uptr->flags |= UNIT_UNLOAD; /* so set the unload flag */
uptr->STATUS |= S2_ATTENTION; /* and Attention status */
}
dpprintf (cvptr->device, DL_DEB_CMD, "RUN/STOP switch set to %s\n",
(load ? "RUN" : "STOP"));
if (cvptr->type == MAC && cvptr->state == Idle_State) /* if this is a MAC controller, and it's idle */
return SCPE_INCOMP; /* then the controller must be called to poll the drives */
} /* otherwise indicate that the command is complete */
return SCPE_OK; /* return normal completion status to the caller */
}
/* Set the drive model.
This validation routine is called to set the model of the disc drive
associated with the specified unit. The "value" parameter indicates the
model ID, and the unit capacity is set to the size indicated.
Implementation notes:
1. If the drive is changed from a 7905 or 7906, which has separate head
protect switches, to a 7920 or 7925, which has a single protect switch,
ensure that both protect bits are set so that all heads are protected.
*/
t_stat dl_set_model (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
if (uptr->flags & UNIT_ATT) /* if the unit is currently attached */
return SCPE_ALATT; /* then the disc model cannot be changed */
uptr->capac = drive_props [GET_MODEL (value)].words; /* set the capacity to the new value */
if (uptr->flags & UNIT_PROT /* if either protect bit is set */
&& (value == UNIT_7920 || value == UNIT_7925)) /* and the new drive is a 7920 or 7925 */
uptr->flags |= UNIT_PROT; /* then ensure that both bits are set */
return SCPE_OK;
}
/* Set or clear the write protection status.
This validation routine is called to set the write protection status of the
disc drive associated with the specified unit. The "value" parameter
indicates whether the drive is to be protected (1) or unprotected (0).
In hardware, the 7920 and 7925 drives have a READ ONLY switch that write-
protects all heads in the drive, and Read-Only status directly reflects the
position of the switch. The switch is simulated by the SET <unit> PROTECT
and SET <unit> UNPROTECT commands.
The 7905 and 7906 drives have separate PROTECT UPPER DISC and PROTECT LOWER
DISC switches that protect heads 0-1 and 2 (7905) or 2-3 (7906),
respectively, and Read-Only status reflects the position of the switch
associated with the head currently addressed by the drive's Head Register.
The Head Register is loaded by the controller as part of a Seek or Cold Load
Read command, or during an auto-seek or spare track seek, if permitted by the
file mask. A Controller Preset command sent to a drive clears the Head
Register, so Read-Only status reflects the PROTECT UPPER DISC switch setting.
The SET <unit> PROTECT=(UPPER | LOWER) and SET <unit> UNPROTECT=(UPPER |
LOWER) simulation commands are provided to protect or unprotect the upper or
lower heads individually. If the option values are omitted, e.g., SET <unit>
PROTECT, then both upper and lower heads are (un)protected.
*/
t_stat dl_set_protect (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
const uint32 model = uptr->flags & UNIT_MODEL;
char gbuf [CBUFSIZE];
if (cptr == NULL) /* if there are no arguments */
if (value) /* then if setting the protection status */
uptr->flags |= UNIT_PROT; /* then protect all heads */
else /* otherwise */
uptr->flags &= ~UNIT_PROT; /* unprotect all heads */
else if (*cptr == '\0') /* otherwise if the argument is empty */
return SCPE_MISVAL; /* then reject the command */
else if (model == UNIT_7920 || model == UNIT_7925) /* otherwise if this is a 7920 or 7925 */
return SCPE_ARG; /* then the heads cannot be protected separately */
else { /* otherwise a 7905/06 argument is present */
cptr = get_glyph (cptr, gbuf, ';'); /* get the argument */
if (strcmp ("LOWER", gbuf) == 0) /* if the LOWER option was specified */
if (value) /* then if setting the protection status */
uptr->flags |= UNIT_PROT_L; /* then set the protect lower disc flag */
else /* otherwise */
uptr->flags &= ~UNIT_PROT_L; /* clear the protect lower disc flag */
else if (strcmp ("UPPER", gbuf) == 0) /* otherwise if the UPPER option was specified */
if (value) /* then if setting the protection status */
uptr->flags |= UNIT_PROT_U; /* then set the protect upper disc flag */
else /* otherwise */
uptr->flags &= ~UNIT_PROT_U; /* clear the protect upper disc flag */
else /* otherwise some other argument was given */
return SCPE_ARG; /* so report the error */
}
return SCPE_OK;
}
/* Show the write protection status.
This display routine is called to show the write protection status of the
disc drive associated with the specified unit. The "value" and "desc"
parameters are unused.
The unit flags contain two bits that indicate write protection for heads 0-1
and heads 2-n. If both bits are clear, the drive is unprotected. A 7905 or
7906 drive may have one or the other or both bits set, indicating that the
upper, lower, or both platters are protected. A 7920 or 7925 will have both
bits set, indicating that the entire drive is protected.
*/
t_stat dl_show_protect (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
const uint32 model = uptr->flags & UNIT_MODEL;
if ((uptr->flags & UNIT_PROT) == 0) /* if the protection flags are clear */
fputs ("unprotected", st); /* then report the disc as unprotected */
else if (model == UNIT_7905 || model == UNIT_7906) /* otherwise if this is a 7905/06 */
if ((uptr->flags & UNIT_PROT) == UNIT_PROT_L) /* then if only the lower disc protect flag is set */
fputs ("lower protected", st); /* then report it */
else if ((uptr->flags & UNIT_PROT) == UNIT_PROT_U) /* otherwise if only the upper disc protect flag is set */
fputs ("upper protected", st); /* then report it */
else /* otherwise both flags are set */
fputs ("lower/upper protected", st); /* so report them */
else /* otherwise it's a 7920/25 */
fputs ("protected", st); /* so either flag set indicates a protected disc */
return SCPE_OK;
}
/* Set or clear the diagnostic override table.
This validation routine is called to set or clear the diagnostic override
table associated with the specified unit. The "value" parameter is either
the positive maximum table entry count if an entry is to be added, or zero if
the table is to be cleared, and "desc" is a pointer to the controller.
If the CPU interface declares a diagnostic override table, it will specify
the table when initializing the controller variables structure with the
CNTLR_INIT macro. This sets the "dop_base" pointer in the structure to point
at the table. If the interface does not declare a table, the pointer will be
NULL.
If the table is present, new entries may be added with this command:
SET <dev> DIAG=<cylinder>;<head>;<sector>;<opcode>;<spd>;<status>
The cylinder, head, and sector values are entered as decimal numbers, the
opcode and status values are entered as octal numbers, and the SPD value is
specified as any combination of the letters "S", "P", or "D".
If a command specifies the opcode value as Request Syndrome (15) and the
status value as Correctable Data Error (17), then four additional values must
be specified as part of the command line above:
;<displacement>;<syndrome 1>;<syndrome 2>;<syndrome 3>
The displacement value is entered as a decimal number, and the three syndrome
values are entered as octal numbers.
Entering SET <dev> DIAG by itself resets the current entry pointer to the
first table entry. Entering SET <dev> NODIAG clears the table.
If the override table was not declared by the CPU interface, the routine
returns "Command not allowed." If SET NODIAG was entered, the "value"
parameter will be -1, and the table will be cleared by storing the special
value DL_OVEND into the first table entry. Otherwise, if SET DIAG is
entered, the table pointer is reset. However, if the table is already empty,
the routine returns "Missing value" to indicate that the table must be
populated first.
If a new entry is to be added, the "value" parameter will indicate the size
of the table in entries. If no free entry exists, the routine returns
"Memory exhausted". Otherwise, the supplied parameters are parsed and
entered into the table. An array of maximum allowed values and radixes are
used during parsing to validate each parameter. The non-numeric SPD
parameter is parsed separately. If, after parsing the first six parameters,
the opcode and status are Request Syndrome and Correctable Data Error,
respectively, then four more parameters are sought. These are stored into
the following table entry.
Implementation notes:
1. Each maximum array element value limits the absolute value of its
corresponding numeric parameter. Negative values may be specified for
unsigned values; no error is returned, and these simply will never match
their intended controller values.
2. Each radix array element value is used to parse its corresponding
parameter. A radix of 0 is used to indicate the S/P/D parameter, which
is parsed alphabetically rather than numerically.
3. One table entry is always reserved for the end-of-table value, so the
number of configurable entries is one less than the defined table size.
*/
t_stat dl_set_diag (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
typedef struct {
t_value max; /* maximum allowed value */
uint32 radix; /* numeric parsing radix */
} PARSER_PROP;
static const PARSER_PROP param [] = {
/* maximum value radix */
/* ------------- ----- */
{ 822, 10 }, /* cylinder */
{ 8, 10 }, /* head */
{ 63, 10 }, /* sector */
{ LAST_OPCODE, 8 }, /* opcode */
{ 0, 0 }, /* SPD */
{ LAST_STATUS, 8 }, /* status */
{ 135, 10 }, /* displacement */
{ D16_UMAX, 8 }, /* syndrome 1 */
{ D16_UMAX, 8 }, /* syndrome 2 */
{ D16_UMAX, 8 } /* syndrome 3 */
};
const CVPTR cvptr = (CVPTR) desc;
DIAG_ENTRY *entry;
uint32 pidx, params [10];
t_stat status;
char gbuf [CBUFSIZE];
if (cvptr->dop_base == NULL) /* if the override array is not present */
return SCPE_NOFNC; /* then the command is not allowed */
else if (value == 0) /* otherwise if this is a NODIAG call */
if (cptr != NULL) /* then if something follows the keyword */
return SCPE_2MARG; /* then report an error */
else { /* otherwise the command is valid */
cvptr->dop_index = -1; /* so clear the current entry pointer */
cvptr->dop_base->cylinder = DL_OVEND; /* and mark the first entry as the end */
}
else if (cptr == NULL) /* otherwise if DIAG is by itself */
if (cvptr->dop_base->cylinder == DL_OVEND) /* then if there are no entries in the table */
return SCPE_MISVAL; /* then one must be entered first */
else /* otherwise */
cvptr->dop_index = 0; /* reset the current pointer to the first entry */
else if (*cptr == '\0') /* otherwise if there are no parameters */
return SCPE_MISVAL; /* then report a missing value */
else { /* otherwise at least one parameter is present */
for (entry = cvptr->dop_base; /* find the */
entry->cylinder != DL_OVEND && value > 0; /* last entry */
entry++, value--); /* in the current table */
if (value <= 1) /* if there's not enough room to add a new entry */
return SCPE_MEM; /* then report the error to the user */
else { /* otherwise there's at least one free entry */
for (pidx = 0; pidx < 10; pidx++) { /* so assume a full set of arguments are present */
if (*cptr == '\0') /* if the next argument is not there */
return SCPE_2FARG; /* then report it missing */
if (param [pidx].radix == 0) { /* if this is the SPD argument */
params [pidx] = 0; /* then parse it specially */
while (*cptr != ';' && *cptr != '\0') { /* look for multiple arguments */
if (*cptr == 'S') /* if it's an S */
params [pidx] |= CM_SPARE; /* then set the spare bit */
else if (*cptr == 'P') /* or if it's a P */
params [pidx] |= CM_PROTECTED; /* then set the protected bit */
else if (*cptr == 'D') /* or if it's a D */
params [pidx] |= CM_DEFECTIVE; /* then set the defective bit */
else /* any other character */
return SCPE_ARG; /* results in an invalid argument error */
cptr++; /* point at the next character and continue */
} /* until a separator or terminator is seen */
if (*cptr == ';') /* if a separator was seen */
cptr++; /* then move past it */
status = SCPE_OK; /* reassure the compiler that status is not uninitialized */
}
else { /* otherwise parse a numeric argument */
cptr = get_glyph (cptr, gbuf, ';'); /* get the argument */
if (gbuf [0] == '-') { /* if the argument is negative */
gbuf [0] = ' '; /* then clear the sign */
params [pidx] = /* and negate the resulting value */
(uint32) NEG16 (get_uint (gbuf, param [pidx].radix,
param [pidx].max, &status));
}
else /* otherwise the argument is unsigned */
params [pidx] = /* so use the value as is */
(uint32) get_uint (gbuf, param [pidx].radix,
param [pidx].max, &status);
}
if (status != SCPE_OK) /* if an error occurred */
return status; /* then return the parsing status */
if (pidx == 5 /* if we have parsed the status */
&& (params [3] != Request_Syndrome /* and this is not a Request Syndrome entry */
|| params [5] != Correctable_Data_Error)) /* with Correctable Data Error status */
break; /* then no more parameters are expected */
}
if (*cptr != '\0') /* if more characters are present */
return SCPE_2MARG; /* then report the excess */
else if (pidx == 10 && value <= 2) /* otherwise if we have syndrome values but no space */
return SCPE_MEM; /* the report that we can't store them */
entry->cylinder = params [0]; /* store the */
entry->head = params [1]; /* first set */
entry->sector = params [2]; /* of parameter values */
entry->opcode = (CNTLR_OPCODE) params [3]; /* in the */
entry->spd = params [4]; /* first available */
entry->status = (CNTLR_STATUS) params [5]; /* empty entry */
if (pidx == 10) { /* if syndrome values were present */
entry++; /* then store them in the next available entry */
entry->spd = params [6]; /* save the displacement */
entry->cylinder = params [7]; /* and the */
entry->head = params [8]; /* three syndrome */
entry->sector = params [9]; /* values */
entry->opcode = Request_Syndrome; /* identify the entry */
entry->status = Correctable_Data_Error; /* by opcode and status */
}
entry++; /* point at the next available entry */
entry->cylinder = DL_OVEND; /* and mark it as the end of the list */
cvptr->dop_index = 0; /* reset the current pointer to the start of the list */
}
}
return SCPE_OK;
}
/* Show the diagnostic override table.
This display routine is called to show the contents of the diagnostic
override table. The "value" parameter is either the positive maximum table
entry count if the routine was invoked by a SHOW <dev> DIAG command, or -1 if
the routine was invoked by a SHOW <dev> command. The "desc" parameter is a
pointer to the controller.
If the override table was not declared by the CPU interface, the routine
returns "Command not allowed." Otherwise, if the table is empty, the routine
prints "override disabled". If the table is populated, then the routine
prints "override enabled" if it was invoked as part of a general SHOW for the
device, or it prints the individual table entries if it was invoked as SHOW
<dev> DIAG.
Entries are printed in tabular form with the columns corresponding to the SET
DIAG parameters, except that the opcode and status fields are decoded. A
Request Syndrome command entry with Correctable Data Error status is followed
by an indented second line containing the displacement and syndrome values.
Numeric values are printed in the same radix as is used to enter them.
Implementation notes:
1. The Request Syndrome displacement parameter is a 16-bit signed value
contained in a 32-bit unsigned array element. To print it properly, we
convert the latter to a 16-bit signed value and then sign-extend to "int"
size for fprintf.
2. The explicit use of "const CNTLR_VARS *" is necessary to declare a
pointer to a constant structure. Using "const CVPTR" declares a constant
pointer instead.
*/
t_stat dl_show_diag (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
const CNTLR_VARS *cvptr = (const CNTLR_VARS *) desc; /* the controller pointer is supplied */
DIAG_ENTRY *entry;
if (cvptr->dop_base == NULL) /* if the table isn't defined */
return SCPE_NOFNC; /* then the command is illegal */
else if (cvptr->dop_index < 0) { /* otherwise if overrides are currently disabled */
fputs ("override disabled", st); /* then report it */
if (value > 0) /* if we were invoked by a SHOW DIAG command */
fputc ('\n', st); /* then we must add the line terminator */
}
else if (value < 0) /* otherwise if we were invoked by a SHOW <dev> command */
fputs ("override enabled", st); /* then print the table status instead of the details */
else for (entry = cvptr->dop_base; /* otherwise print each table entry */
entry->cylinder != DL_OVEND && value > 0; /* until the end-of-table marker or count exhaustion */
entry++, value--) {
fprintf (st, "%3d %1d %2d %*s %c%c%c %*s\n", /* print the entry */
entry->cylinder, entry->head, entry->sector,
- OPCODE_LENGTH, dl_opcode_name (cvptr->type, entry->opcode),
(entry->spd & CM_SPARE ? 'S' : ' '),
(entry->spd & CM_PROTECTED ? 'P' : ' '),
(entry->spd & CM_DEFECTIVE ? 'D' : ' '),
- STATUS_LENGTH, dl_status_name (entry->status));
if (entry->opcode == Request_Syndrome /* if the current entry is a syndrome request */
&& entry->status == Correctable_Data_Error) { /* for a correctable data error */
entry++; /* then the next entry contains the values */
value = value - 1; /* drop the entry count to account for it */
fprintf (st, " %3d %06o %06o %06o\n", /* print the displacement and syndrome values */
(int) INT16 (entry->spd),
entry->cylinder, entry->head, entry->sector);
}
}
return SCPE_OK;
}
/* Set the controller timing mode.
This validation routine is called to set the timing mode for the disc
subsystem. As this is an extended MTAB call, the "uptr" parameter points to
the unit array of the device. The "value" parameter is set non-zero to use
realistic timing and 0 to use fast timing. For a MAC controller, the "desc"
parameter is a pointer to the controller. For ICD controllers, the "desc"
parameter is a pointer to the first element of the controller array. There
must be one controller for each unit defined by the device associated with
the controllers. The "cptr" parameter is not used.
If fast timing is selected, the controller's timing pointer is set to the
fast timing pointer supplied by the interface when the controller was
initialized. If real timing is selected, the table of real times is searched
for an entry whose controller type matches the supplied controller. MAC and
ICD controllers support several drive models with identical timing
characteristics. However, CS/80 controllers allow mixing drives with
different timing on a single CPU interface. For these, the drive type must
match as well. If a match is found, the controller's timing pointer is set
to the associated real-time entry. Otherwise, the routine returns SCPE_IERR.
Non-MAC controllers are dedicated per-drive, so "desc" points not at a single
controller instance but at an array of controller instances. The timing mode
is common to every controller in the array.
*/
t_stat dl_set_timing (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
CVPTR cvptr = (CVPTR) desc; /* the controller pointer is supplied */
const DELAY_PROPS *dpptr;
DRIVE_TYPE model;
uint32 delay, cntlr_count;
if (cvptr->type == MAC) /* if this is a MAC controller */
cntlr_count = 1; /* then there is one controller for all units */
else /* otherwise */
cntlr_count = cvptr->device->numunits; /* there is one controller per unit */
while (cntlr_count--) { /* set each controller's timing mode */
if (value) { /* if realistic timing is requested */
model = GET_MODEL (uptr->flags); /* then get the drive model from the current unit */
dpptr = real_times; /* and reset the real-time delay table pointer */
for (delay = 0; delay < DELAY_COUNT; delay++) /* search for the correct set of times */
if (dpptr->type == cvptr->type /* if the controller types match */
&& (dpptr->drive == HP_All /* and all drive times are the same */
|| dpptr->drive == model)) { /* or the drive types match as well */
cvptr->dlyptr = dpptr; /* then use this set of times */
break;
}
else /* otherwise */
dpptr++; /* point at the next array element */
if (delay == DELAY_COUNT) /* if the model was not found in the table */
return SCPE_IERR; /* then report an internal (impossible) error */
else /* otherwise */
cvptr->device->flags |= DEV_REALTIME; /* set the real time flag */
}
else { /* otherwise fast timing is requested */
cvptr->device->flags &= ~DEV_REALTIME; /* so clear the real time flag */
cvptr->dlyptr = cvptr->fastptr; /* and set the delays to the FASTTIME settings */
}
cvptr++; /* point at the next controller */
uptr++; /* and corresponding unit for non-MAC controllers */
}
return SCPE_OK;
}
/* Show the controller timing mode
This display routine is called to show the timing mode for the disc
subsystem. The "value" parameter is unused; the "desc" parameter is a
pointer to the controller.
Implementation notes:
1. The explicit use of "const CNTLR_VARS *" is necessary to declare a
pointer to a constant structure. Using "const CVPTR" declares a constant
pointer instead.
*/
t_stat dl_show_timing (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
{
const CNTLR_VARS *cvptr = (const CNTLR_VARS *) desc; /* the controller pointer is supplied */
if (cvptr->device->flags & DEV_REALTIME) /* if the real time flag is set */
fputs ("realistic timing", st); /* then we're using realistic timing */
else /* otherwise */
fputs ("fast timing", st); /* we're using optimized timing */
return SCPE_OK;
}
/* Disc library local controller routines */
/* Start or stop the command/parameter wait timer.
A MAC controller uses a 1.74 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 additional controller unit that is activated when the
command or parameter wait timer is started and cancelled when the timer is
stopped.
ICD interfaces do not use the wait timer or supply an additional unit.
Implementation notes:
1. Absolute activation is used because the timer is restarted between
parameter word transfers.
*/
static void wait_timer (CVPTR cvptr, FLIP_FLOP action)
{
if (cvptr->type == MAC) /* if this a MAC controller */
if (action == SET) /* then if the timer is to be set */
sim_activate_abs (CNTLR_UPTR, CNTLR_TIMEOUT); /* then activate the controller unit */
else { /* otherwise the timer is to be stopped */
sim_cancel (CNTLR_UPTR); /* so cancel the unit */
CNTLR_UPTR->PHASE = Idle_Phase; /* and idle the controller unit */
}
return;
}
/* 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).
*/
static void idle_controller (CVPTR cvptr)
{
wait_timer (cvptr, CLEAR); /* stop the command wait timer */
cvptr->status = Normal_Completion; /* the Poll Loop clears the status */
cvptr->state = Idle_State; /* idle the controller */
return;
}
/* End the current command.
The currently executing command is completed with the supplied status. If
the command completed normally, and it returns to the Poll Loop, the
controller is idled, and the wait timer is cancelled. Otherwise, the
controller enters the Wait Loop, and the wait timer is started. If the
command had accessed a drive unit, the unit is idled. Also, for a MAC
controller, the controller unit is idled as well.
*/
static void end_command (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status)
{
cvptr->status = status; /* set the command result status */
if (status == Normal_Completion /* if the command completed normally */
&& cmd_props [cvptr->opcode].idle_at_end) { /* and this command idles the controller */
cvptr->state = Idle_State; /* then set idle status */
wait_timer (cvptr, CLEAR); /* and stop the command wait timer */
}
else { /* otherwise */
cvptr->state = Wait_State; /* the controller waits for a new command */
wait_timer (cvptr, SET); /* so start the command wait timer */
if (uptr) /* if the command accessed a drive */
uptr->PHASE = Idle_Phase; /* then idle it */
if (cvptr->type == MAC) /* if this is a MAC controller */
CNTLR_UPTR->PHASE = Idle_Phase; /* then idle the controller unit as well */
}
if (cmd_props [cvptr->opcode].transfer_size > 0)
dpprintf (cvptr->device, DL_DEB_CMD, (cvptr->opcode == Initialize
? "Unit %u Initialize %s for %u words (%u sector%s)\n"
: "Unit %u %s for %u words (%u sector%s)\n"),
CM_UNIT (cvptr->spd_unit),
(cvptr->opcode == Initialize
? fmt_bitset (cvptr->spd_unit, initialize_format)
: opcode_name [cvptr->opcode]),
cvptr->count,
cvptr->count / cmd_props [cvptr->opcode].transfer_size + (cvptr->length > 0),
(cvptr->count <= cmd_props [cvptr->opcode].transfer_size ? "" : "s"));
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %u %s command completed with %s status\n",
CM_UNIT (cvptr->spd_unit), opcode_name [cvptr->opcode],
dl_status_name (cvptr->status));
return;
}
/* Start a read operation on the current sector.
This routine is called at the end of the rotate phase to begin a read
operation. The current sector given 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.
The routine returns TRUE if the data is ready to be transferred and FALSE if
it is not (due to command completion, an error, or an auto-seek that must
complete first).
On entry, the end-of-data flag is checked. If it is set, the current read
command 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 Seek_Phase set 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 track, 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 read
failed with a host file system error, it is reported to the simulation
console and the read ends with an Uncorrectable Data Error. If it succeeded
but did not return a full sector, the remainder of the buffer is padded with
zeros.
If the image was read correctly, the operation phase is set for the data
transfer, the index of the first word to transfer is set, and the routine
returns TRUE to begin the data transfer.
Implementation notes:
1. This routine changes the unit phase state as follows:
Rotate_Phase => Idle_Phase if EOD or error (returns FALSE)
Rotate_Phase => Seek_Phase if auto-seek (returns FALSE)
Rotate_Phase => Data_Phase otherwise (returns TRUE)
2. The position_sector routine sets up the data phase if it succeeds or the
seek phase if a seek is required.
*/
static t_bool start_read (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags)
{
uint32 count, offset;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OPCODE;
if (flags & EOD) { /* if the end of data is indicated */
end_command (cvptr, uptr, Normal_Completion); /* then complete the command */
return FALSE; /* and end the current operation */
}
if (opcode == Read_Full_Sector) { /* if we are starting a Read Full Sector command */
if (cvptr->type == MAC) /* then if this is a MAC controller */
cvptr->buffer [0] = 0100376; /* indicate that ECC support is valid */
else /* otherwise */
cvptr->buffer [0] = 0100377; /* indicate that ECC support is not available */
set_address (cvptr, 1); /* set the current address into buffer words 1-2 */
offset = 3; /* and start the data after the header */
}
else /* otherwise it's a normal read command */
offset = 0; /* so data starts at the beginning of the buffer */
if (position_sector (cvptr, uptr) == FALSE) /* position the sector; if it was not */
return FALSE; /* then a seek is in progress or an error occurred */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %d %s from cylinder %u head %u sector %u\n",
(int32) (uptr - cvptr->device->units), opcode_name [opcode],
uptr->CYL, cvptr->head, cvptr->sector);
count = sim_fread (cvptr->buffer + offset, /* read the sector from the image */
sizeof (DL_BUFFER), /* into the sector buffer */
WORDS_PER_SECTOR, uptr->fileref);
if (ferror (uptr->fileref)) { /* if a host file system error occurred */
io_error (cvptr, uptr, Uncorrectable_Data_Error); /* then report it to the simulation console */
return FALSE; /* and terminate with Uncorrectable Data Error status */
}
cvptr->length = cmd_props [opcode].transfer_size; /* set the appropriate transfer length */
cvptr->index = 0; /* and reset the data index */
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 */
return TRUE; /* the read was successfully started */
}
/* Finish a read operation on the current sector.
This routine is called at the end of the intersector phase to finish a read
operation. Command termination conditions are checked, and the next sector
is addressed in preparation for the read to continue.
On entry, the diagnostic override status is checked. If it is set, then the
read is terminated with the indicated status. Otherwise, the data overrun
flag is checked. If it is set, the read is terminated with an error.
Otherwise, the next sector is addressed.
If the end-of-data flag is set, the current read is completed. Otherwise,the
rotate phase is set up in preparation for the next sector read.
Implementation notes:
1. This routine changes the unit phase state as follows:
Intersector_Phase => Idle_Phase if EOD or error
Intersector_Phase => Rotate_Phase otherwise
2. The HP 1000 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 explicitly.
Note also that the delay is a fixed number of instructions, regardless of
timing mode, to ensure that the CPU makes it through the driver code to
output the untalk command.
*/
static void end_read (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags)
{
uint32 bound;
if (cvptr->status != Normal_Completion) /* if a diagnostic override is present */
end_command (cvptr, uptr, cvptr->status); /* then report the indicated status */
else if (flags & OVRUN) /* otherwise if a read overrun occurred */
end_command (cvptr, uptr, Data_Overrun); /* then terminate the command with an error */
else { /* otherwise the read succeeded */
next_sector (cvptr, uptr); /* so address the next sector */
if (flags & EOD) /* if the end of data is indicated */
end_command (cvptr, uptr, Normal_Completion); /* then complete the command */
else { /* otherwise reading continues */
uptr->PHASE = Rotate_Phase; /* so set up the unit for the rotate phase */
uptr->wait = cvptr->dlyptr->intersector_gap; /* with a delay for the intersector time */
if (cvptr->eoc == SET && cvptr->type == ICD) { /* if a seek will be required on an ICD controller */
if ((cvptr->file_mask & CM_AUTO_SEEK_EN) == 0) /* then if auto-seek is disabled */
bound = cvptr->cylinder; /* then the bound is the current cylinder */
else if (cvptr->file_mask & CM_DECR_SEEK) /* otherwise if a decremental seek is enabled */
bound = 0; /* then the bound is cylinder 0 */
else /* otherwise the enabled bound is the last cylinder */
bound = drive_props [GET_MODEL (uptr->flags)].cylinders - 1;
if (cvptr->cylinder == bound) /* if the positioner is already at the bound */
uptr->wait = UNTALK_DELAY; /* then the seek will fail; delay to allow CPU to untalk */
}
}
}
return;
}
/* Start a write operation on the current sector.
This routine is called at the end of the rotate phase to begin a write
operation. 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. The routine returns TRUE if the data is ready to be
transferred and FALSE if it is not (due to an error or an auto-seek that must
complete first).
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 Seek_Phase set 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 track, the operation phase is set
for the data transfer, the index of the first word to transfer is set, and
the routine returns TRUE to begin the data transfer.
Implementation notes:
1. This routine changes the unit phase state as follows:
Rotate_Phase => Idle_Phase if write protected or error (returns FALSE)
Rotate_Phase => Seek_Phase if auto-seek (returns FALSE)
Rotate_Phase => Data_Phase otherwise (returns TRUE)
2. The position_sector routine sets up the data phase if it succeeds or the
seek phase if a seek is required.
*/
static t_bool start_write (CVPTR cvptr, UNIT *uptr)
{
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OPCODE;
if (opcode == Write /* if this is a Write command */
&& cvptr->spd_unit & CM_PROTECTED /* and the track is protected */
&& (uptr->flags & UNIT_FMT) == 0) /* but the FORMAT switch is not set */
end_command (cvptr, uptr, Protected_Track); /* then fail with a protection error */
else if (uptr->STATUS & S2_READ_ONLY /* otherwise if the unit is write protected */
|| opcode != Write && (uptr->flags & UNIT_FMT) == 0) /* or the FORMAT switch must be set but is not */
end_command (cvptr, uptr, Status_2_Error); /* then fail with a status error */
else if (position_sector (cvptr, uptr) == TRUE) { /* otherwise if positioning the sector succeeded */
cvptr->length = cmd_props [opcode].transfer_size; /* then set the appropriate transfer length */
cvptr->index = 0; /* and reset the data index */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %d %s to cylinder %u head %u sector %u\n",
(int32) (uptr - cvptr->device->units), opcode_name [opcode],
uptr->CYL, cvptr->head, cvptr->sector);
return TRUE; /* the write was successfully started */
}
return FALSE; /* otherwise an error occurred or a seek is required */
}
/* Finish a write operation on the current sector.
This routine is called at the end of the intersector phase to finish a write
operation. 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 three header words at the start of the buffer that are
not written to the disc image).
If the image write failed with a host file system error, it is reported to
the simulation console and the write ends with an Uncorrectable Data Error.
If it succeeded, the diagnostic override status is checked. If it is set,
then the write is terminated with the indicated status. Otherwise, the data
overrun flag is checked. If it is set, the write is terminated with an
error. Otherwise, the next sector is addressed. If the end-of-data flag is
set, the current write is completed. Otherwise,the rotate phase is set up in
preparation for the next sector write.
Implementation notes:
1. This routine changes the unit phase state as follows:
Intersector_Phase => Idle_Phase if EOD or error
Intersector_Phase => Rotate_Phase otherwise
2. A partial sector is filled either with octal 177777 words (ICD) or copies
of the last word (MAC), per page 7-10 of the ICD/MAC Disc Diagnostic
manual.
*/
static void end_write (CVPTR cvptr, UNIT *uptr, CNTLR_FLAG_SET flags)
{
uint32 count;
DL_BUFFER pad;
const CNTLR_OPCODE opcode = (CNTLR_OPCODE) uptr->OPCODE;
const uint32 offset = (opcode == Write_Full_Sector ? 3 : 0);
if (uptr->flags & UNIT_UNLOAD) { /* if the drive is not ready */
end_command (cvptr, uptr, Access_Not_Ready); /* then terminate the command */
return; /* with an access error */
}
if (cvptr->index < WORDS_PER_SECTOR + offset) { /* if a partial sector was transferred */
if (cvptr->type == ICD) /* then an ICD controller */
pad = D16_UMAX; /* pads the sector with -1 words */
else /* whereas a MAC controller */
pad = cvptr->buffer [cvptr->index - 1]; /* pads with the last word written */
for (count = cvptr->index; count < WORDS_PER_SECTOR + offset; count++)
cvptr->buffer [count] = pad; /* pad the sector buffer as needed */
}
sim_fwrite (cvptr->buffer + offset, sizeof (DL_BUFFER), /* write the sector to the file */
WORDS_PER_SECTOR, uptr->fileref);
if (ferror (uptr->fileref)) /* if a host file system error occurred, then report it */
io_error (cvptr, uptr, Uncorrectable_Data_Error); /* and terminate with Uncorrectable Data Error status */
else if (cvptr->status != Normal_Completion) /* otherwise if a diagnostic override is present */
end_command (cvptr, uptr, cvptr->status); /* then report the indicated status */
else if (flags & OVRUN) /* otherwise if a write overrun occurred */
end_command (cvptr, uptr, Data_Overrun); /* then terminate the command with an error */
else { /* otherwise the write succeeded */
next_sector (cvptr, uptr); /* so address the next sector */
if (flags & EOD) /* if the end of data is indicated */
end_command (cvptr, uptr, Normal_Completion); /* then complete the command */
else { /* otherwise writing continues */
uptr->PHASE = Rotate_Phase; /* so set up the unit for the rotate phase */
uptr->wait = cvptr->dlyptr->intersector_gap; /* with a delay for the intersector time */
}
}
return;
}
/* 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, the diagnostic override status is checked. If it is set to
anything other than a data error, then positioning is terminated with the
indicated status to simulate an address verification failure. Otherwise, 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 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.
Otherwise, the seek is legal, and the routine returns with the seek phase set
to wait for seek completion before resuming the current read or 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 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 unit set to the seek phase
to wait for seek completion as above.
If the drive and controller positions agree or address 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 file is positioned to a byte offset in the
image file that is calculated from the CHS address. If positioning succeeds,
the data phase is set up to begin the data transfer, and the routine returns
TRUE to indicate that the file position is set. If positioning fails with a
host file system error, it is reported to the simulation console, and the
routine returns FALSE to indicate that an AGC (drive positioner) fault
occurred.
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)
{
const DRIVE_TYPE model = GET_MODEL (uptr->flags); /* get the drive model */
if (cvptr->status != Normal_Completion /* if a diagnostic override is present */
&& cvptr->status != Uncorrectable_Data_Error /* and it's not */
&& cvptr->status != Correctable_Data_Error) /* a data error */
end_command (cvptr, uptr, cvptr->status); /* then report it */
else if (cvptr->eoc == SET) /* otherwise if we are at the end of a cylinder */
if (cvptr->file_mask & CM_AUTO_SEEK_EN) { /* then if an auto-seek is allowed */
if (cvptr->file_mask & CM_DECR_SEEK) /* then if a decremental seek is requested */
cvptr->cylinder = cvptr->cylinder - 1 & D16_MASK; /* then decrease the address with wraparound */
else /* otherwise an incremental seek is requested */
cvptr->cylinder = cvptr->cylinder + 1 & D16_MASK; /* so increase the address with wraparound */
start_seek (cvptr, uptr); /* start the auto-seek */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %d %s%s autoseek to cylinder %u head %u sector %u\n",
(int32) (uptr - cvptr->device->units), opcode_name [uptr->OPCODE],
(uptr->STATUS & S2_SEEK_CHECK ? " seek check on" : ""),
cvptr->cylinder, cvptr->head, cvptr->sector);
if (uptr->STATUS & S2_SEEK_CHECK) /* if a seek check occurred */
if (cvptr->type == ICD) /* then if this is an ICD controller */
end_command (cvptr, uptr, End_of_Cylinder); /* then report it as an End of Cylinder error */
else /* otherwise */
end_command (cvptr, uptr, Status_2_Error); /* report it as a Status-2 error */
}
else /* otherwise the file mask does not permit an auto-seek */
end_command (cvptr, uptr, End_of_Cylinder); /* so terminate with an EOC error */
else if (cvptr->verify /* if address verification is enabled */
&& (uint32) uptr->CYL != cvptr->cylinder) { /* and the positioner is on the wrong cylinder */
start_seek (cvptr, uptr); /* then start a seek to the correct cylinder */
dpprintf (cvptr->device, DL_DEB_INCO, "Unit %d %s%s reseek to cylinder %u head %u sector %u\n",
(int32) (uptr - cvptr->device->units), opcode_name [uptr->OPCODE],
(uptr->STATUS & S2_SEEK_CHECK ? " seek check on" : ""),
cvptr->cylinder, cvptr->head, cvptr->sector);
if (uptr->STATUS & S2_SEEK_CHECK) /* if a seek check occurred */
end_command (cvptr, uptr, Status_2_Error); /* then report a Status-2 error */
}
else if (((uint32) uptr->CYL >= drive_props [model].cylinders) /* otherwise the heads are positioned correctly */
|| (cvptr->head >= drive_props [model].heads) /* but if the cylinder */
|| (cvptr->sector >= drive_props [model].sectors)) { /* or head or sector is out of bounds */
uptr->STATUS |= S2_SEEK_CHECK; /* then set Seek Check status */
end_command (cvptr, uptr, Status_2_Error); /* and terminate with an error */
}
else if (uptr->flags & UNIT_UNLOAD) /* otherwise if the drive is not ready for positioning */
end_command (cvptr, uptr, Access_Not_Ready); /* then terminate with an access error */
else { /* otherwise we are ready to move the heads */
set_file_pos (cvptr, uptr, model); /* so calculate the new position */
if (sim_fseek (uptr->fileref, uptr->pos, SEEK_SET)) { /* set the image file position; if it failed */
io_error (cvptr, uptr, Status_2_Error); /* then report it to the simulation console */
dl_load_unload (cvptr, uptr, FALSE); /* unload the heads */
uptr->STATUS |= S2_FAULT; /* and set Fault status */
}
else { /* otherwise the seek succeeded */
uptr->PHASE = Data_Phase; /* so set up the data transfer phase */
if (cvptr->device->flags & DEV_REALTIME) /* if the real time mode is enabled */
uptr->wait = cvptr->dlyptr->data_xfer /* then base the delay on the sector preamble size */
* cmd_props [uptr->OPCODE].preamble_size;
else /* otherwise */
uptr->wait = cvptr->dlyptr->data_xfer; /* start the transfer with a nominal delay */
return TRUE; /* report that positioning was accomplished */
}
}
return FALSE; /* positioning failed or was deferred */
}
/* Address the next sector.
This routine is called after a sector has been successfully read or written
in preparation for continuing the transfer. It is also called after the
Request Syndrome command returns the correction status for a sector in error.
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 and the address verification state is reset to
enable it for a Read_Without_Verify command. 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 DRIVE_TYPE 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) /* if we at not the end of the track */
return; /* then the next sector value is OK */
cvptr->sector = 0; /* otherwise wrap the sector number */
cvptr->verify = cmd_props [uptr->OPCODE].verify_address; /* and set the address verification flag */
if (cvptr->file_mask & CM_CYL_MODE) { /* if the controller is in cylinder mode */
cvptr->head = cvptr->head + 1; /* then increment the head */
if (cvptr->head < drive_props [model].heads) /* if we are not at the end of the cylinder */
return; /* then the next head value is OK */
cvptr->head = 0; /* otherwise wrap the head number */
}
cvptr->eoc = SET; /* set the end-of-cylinder flag */
return; /* to 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. The routine returns TRUE if the unit is seeking and FALSE if
the seek failed to start.
If the drive is not ready, the seek fails immediately with a Status-2 error.
If the drive is already seeking, Seek Check status will occur, and the
routine will return TRUE to allow the current seek to complete normally.
Otherwise, a seek is initiated to cylinder 0 if the current command is
Recalibrate or to the cylinder value stored in the controller if it is not.
EOC is reset for a seek but not for recalibrate, so that a reseek will return
to the same location as was current when the recalibration was done.
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.
In hardware, the controller issues tag bus SEEK and ADR (address record)
commands to the drive to load the drive's cylinder, head, and sector
registers. On the 7905 and 7906 drives, loading the head register
establishes the drive's Read-Only status in conjunction with the PROTECT
UPPER/LOWER DISC switch settings.
A seek check for either the cylinder, head, or sector 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 unit is set to the seek phase, and the scheduling delay is
calculated by the distance the heads traversed (in real time mode), or
it is set to a fixed delay (in fast time mode).
Implementation notes:
1. For ICD drives, a seek check will terminate the command immediately with
a Status-2 error. A seek-in-progress seek check cannot occur on an ICD
drive, however, because the second seek command will not be started until
the first seek completes.
2. 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.
3. The head register contents does not affect Read-Only status on the 7920
or 7925, which is established solely by the switch setting. However, we
set the drive status here anyway as a convenience.
*/
static t_bool start_seek (CVPTR cvptr, UNIT *uptr)
{
int32 delta;
uint32 target_cylinder;
const DRIVE_TYPE model = GET_MODEL (uptr->flags); /* get the drive model */
if (uptr->flags & UNIT_UNLOAD) /* if the heads are unloaded */
return FALSE; /* then the seek fails as the drive was not ready */
else if (uptr->PHASE == Seek_Phase) { /* otherwise if a seek is in progress */
uptr->STATUS |= S2_SEEK_CHECK; /* then set Seek Check status */
return TRUE; /* and return to let the seek complete */
}
else if (uptr->OPCODE == Recalibrate) /* otherwise if the unit is recalibrating */
target_cylinder = 0; /* then seek to cylinder 0 and don't reset the EOC flag */
else { /* otherwise it's a Seek command or an auto-seek request */
target_cylinder = cvptr->cylinder; /* so seek to the controller cylinder */
cvptr->eoc = CLEAR; /* and clear the end-of-cylinder flag */
}
if (target_cylinder >= drive_props [model].cylinders) { /* if the cylinder is out of bounds */
delta = 0; /* then don't change the positioner */
uptr->STATUS |= S2_SEEK_CHECK; /* and set Seek Check status */
}
else { /* otherwise the cylinder value is OK */
delta = abs (uptr->CYL - (int32) 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->STATUS |= S2_SEEK_CHECK; /* then set Seek Check status */
else { /* otherwise the head and sector are OK */
uptr->STATUS &= ~S2_SEEK_CHECK; /* so clear Seek Check status */
if (uptr->flags & /* if the selected head is protected */
(cvptr->head > 1 ? UNIT_PROT_L : UNIT_PROT_U))
uptr->STATUS |= S2_READ_ONLY; /* then set read-only status */
else /* otherwise */
uptr->STATUS &= ~S2_READ_ONLY; /* clear it */
}
}
if (uptr->STATUS & S2_SEEK_CHECK && cvptr->type == ICD) /* if a seek check occurred on an ICD controller */
return FALSE; /* then the command fails immediately */
else { /* otherwise the seek was OK or this is a MAC controller */
uptr->PHASE = Seek_Phase; /* so set the unit to the seek phase */
uptr->wait = cvptr->dlyptr->seek_one /* set the seek delay, based on the relative movement */
+ delta * (cvptr->dlyptr->seek_full - cvptr->dlyptr->seek_one)
/ drive_props [model].cylinders;
}
return TRUE; /* the seek is underway */
}
/* Report a stream I/O error.
Errors indicated by the host file system are printed on the simulation
console, and the current command is terminated with the supplied status
indication. The target OS will respond to the status return appropriately.
*/
static void io_error (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status)
{
cprintf ("%s simulator disc library I/O error: %s\n", /* report the error to the console */
sim_name, strerror (errno));
dpprintf (cvptr->device, cvptr->device->dctrl, "Host system stream I/O call failed: %s\n",
strerror (errno));
clearerr (uptr->fileref); /* clear the error */
end_command (cvptr, uptr, status); /* terminate the command with the supplied status */
return;
}
/* Set up the controller completion.
This routine performs a scheduled "end_command" to complete a command after a
short delay. It is called for commands that execute to completion with no
drive or CPU interface interaction. An otherwise unused "end phase" is
scheduled just so that the command does not appear to complete
instantaneously.
*/
static void set_completion (CVPTR cvptr, UNIT *uptr, CNTLR_STATUS status)
{
cvptr->status = status; /* save the supplied status */
uptr->PHASE = End_Phase; /* schedule the end phase */
uptr->wait = cvptr->dlyptr->overhead / 2; /* with a short delay */
return;
}
/* 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] = /* update the cylinder if EOC is set */
(DL_BUFFER) cvptr->cylinder + (cvptr->eoc == SET ? 1 : 0);
cvptr->buffer [index + 1] = /* merge the head and sector */
(DL_BUFFER) (PO_HEAD (cvptr->head) | PO_SECTOR (cvptr->sector));
return;
}
/* Return the drive status (Status-2).
This routine returns the formatted unit status for the indicated drive unit.
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, Read-Only, First Status, Fault, and Seek Check bits are
stored in the unit status field. The other status bits are determined
dynamically.
2. The Drive Busy bit is set if the unit is in the seek phase. In hardware,
this bit indicates that the heads are not positioned over a track, i.e.,
that a seek is in progress. A Request Status command is accepted only
when the controller is waiting for seek completion or for a new command.
Therefore, the unit will be either in the seek phase or the idle phase,
respectively, when status is returned.
*/
static HP_WORD drive_status (UNIT *uptr)
{
HP_WORD status;
if (uptr == NULL) /* if the unit is invalid */
return S2_ERROR | S2_NOT_READY; /* then it does not respond */
status = /* start with the drive type and unit status */
S2_DRIVE_TYPE (GET_MODEL (uptr->flags)) | uptr->STATUS;
if (uptr->flags & UNIT_FMT) /* if the format switch is enabled */
status |= S2_FORMAT_EN; /* then set the Format status bit */
if (uptr->flags & UNIT_DIS) /* if the unit does not exist */
status |= S2_NOT_READY; /* then set the Not Ready bit */
else if (uptr->flags & UNIT_UNLOAD) /* if the heads are unloaded */
status |= S2_NOT_READY | S2_BUSY; /* then set the Not Ready and Drive Busy bits */
if (uptr->PHASE == Seek_Phase) /* if a seek is in progress */
status |= S2_BUSY; /* then set the Drive Busy bit */
if (status & S2_ERRORS) /* if there any Status-2 errors */
status |= S2_ERROR; /* then set the Error summary bit */
return status; /* return the unit status */
}
/* Activate the unit.
The specified unit is activated using the unit's "wait" time. If tracing
is enabled, the activation is logged to the debug file.
Implementation notes:
1. The "%.0u" print specification in the trace call absorbs the zero "unit"
value parameter without printing when the controller unit is specified.
*/
static t_stat activate_unit (CVPTR cvptr, UNIT *uptr)
{
t_stat result;
const int32 unit = (int32) (uptr - cvptr->device->units); /* the unit number */
dpprintf (cvptr->device, DL_DEB_SERV, (unit == CNTLR_UNIT
? "Controller unit%.0d %s %s phase delay %d service scheduled\n"
: "Unit %d %s %s phase delay %d service scheduled\n"),
(unit == CNTLR_UNIT ? 0 : unit), opcode_name [uptr->OPCODE],
phase_name [uptr->PHASE], uptr->wait);
result = sim_activate (uptr, uptr->wait); /* activate the unit */
uptr->wait = NO_EVENT; /* and reset the activation time */
return result; /* return the activation status */
}
/* Set up the rotation phase.
The supplied unit is set to the rotate phase at the start of a read or write
command. In real time mode, the rotational latency is determined by the
distance between the "current" sector location and the target sector
location. The former is estimated from the current "simulation time," which
is the number of event ticks since the simulation run was started, and the
simulated disc rotation time, as follows:
(simulation_time / per_sector_time) MOD sectors_per_track
The distance is then:
(sectors_per_track + target_sector - current_sector) MOD sectors_per_track
...and the latency is then:
distance * per_sector_time
In fast time mode, the latency is fixed at the specified per-sector time.
*/
static void set_rotation (CVPTR cvptr, UNIT *uptr)
{
uint32 sectors_per_track;
double distance;
uptr->PHASE = Rotate_Phase; /* set the phase */
if (cvptr->device->flags & DEV_REALTIME) { /* if the mode is real time */
sectors_per_track = /* then calculate the latency as above */
drive_props [GET_MODEL (uptr->flags)].sectors;
distance =
fmod (sectors_per_track + cvptr->sector - CURRENT_SECTOR (cvptr, uptr),
sectors_per_track);
uptr->wait = (int32) (cvptr->dlyptr->sector_full * distance);
}
else /* otherwise the mode is fast time */
uptr->wait = cvptr->dlyptr->sector_full; /* so use the specified time directly */
}
/* Set the image file position.
A cylinder/head/sector address is converted into a byte offset to pass to the
host file I/O routines. The cylinder is supplied by the drive unit, and the
head and sector addresses are supplied by the controller. The disc image
file is laid out in one or two pieces, depending on whether a fixed platter
is present in the drive. If it is, then the area corresponding to the
removable platter precedes the area corresponding to the fixed platter. If
not, then the file contains a single area encompassing all of the (removable)
heads.
In either case, the target track within the area is:
cylinder * heads_per_cylinder + head
...and the target byte position in the file is:
(target_track * sectors_per_track + sector) * bytes_per_sector
*/
static void set_file_pos (CVPTR cvptr, UNIT *uptr, uint32 model)
{
uint32 track;
if (cvptr->head < drive_props [model].remov_heads) /* if the head is on a removable platter */
track = uptr->CYL * drive_props [model].remov_heads /* then the tracks in the file are contiguous */
+ cvptr->head;
else /* otherwise the head is on a fixed platter */
track = drive_props [model].cylinders /* so the target track is located */
* drive_props [model].remov_heads /* in the second area */
+ uptr->CYL * drive_props [model].fixed_heads /* that is offset from the first */
+ cvptr->head - drive_props [model].remov_heads; /* by the size of the removable platter */
uptr->pos = (track * drive_props [model].sectors + cvptr->sector) /* set the byte offset in the file */
* WORDS_PER_SECTOR * sizeof (DL_BUFFER); /* of the CHS target sector */
return;
}