4345 lines
226 KiB
C
4345 lines
226 KiB
C
/* hp_disclib.c: HP MAC/ICD disc controller simulator library
|
|
|
|
Copyright (c) 2011-2018, 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.
|
|
|
|
27-Dec-18 JDB Revised fall through comments to comply with gcc 7
|
|
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 through into the Initialize case */
|
|
|
|
case Initialize:
|
|
cvptr->spd_unit |= CM_SPD (inbound_data); /* merge the SPD flags with the unit */
|
|
|
|
/* fall through 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 through 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;
|
|
}
|