mirror of
https://github.com/open-simh/simh.git
synced 2026-01-13 15:27:46 +00:00
3556 lines
184 KiB
C
3556 lines
184 KiB
C
/* hp2100_ipl.c: HP 12875A Processor Interconnect simulator
|
|
|
|
Copyright (c) 2002-2016, Robert M. Supnik
|
|
Copyright (c) 2017-2020, J. David Bryan
|
|
|
|
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.
|
|
|
|
IPLI, IPLO 12875A Processor Interconnect
|
|
|
|
14-Aug-20 JDB Improved "wait_event" fallback logic
|
|
26-Jul-20 JDB Added CMD tracing for TSB SP/IOP commands and status
|
|
27-Jun-20 JDB Added SET INTERLOCK and instruction interlocking
|
|
14-Feb-20 JDB Added sys/stat.h include for FreeBSD
|
|
09-Dec-19 JDB Removed unneeded push/pop pragmas for Windows
|
|
19-Nov-19 JDB Imposed input polling order to fix Access printing hang
|
|
18-Mar-19 JDB Reordered SCP includes
|
|
11-Jul-18 JDB Revised I/O model
|
|
22-May-18 JDB Added process synchronization commands
|
|
01-May-18 JDB Removed ioCRS counter, as consecutive ioCRS calls are no longer made
|
|
26-Mar-18 JDB Converted from socket to shared memory connections
|
|
28-Feb-18 JDB Added the special IOP BBL
|
|
13-Aug-17 JDB Revised so that only IPLI boots
|
|
19-Jul-17 JDB Removed unused "ipl_stopioe" variable and register
|
|
11-Jul-17 JDB Renamed "ibl_copy" to "cpu_ibl"
|
|
15-Mar-17 JDB Trace flags are now global
|
|
Changed DEBUG_PRJ calls to tpprintfs
|
|
10-Mar-17 JDB Added IOBUS to the debug table
|
|
27-Feb-17 JDB ibl_copy no longer returns a status code
|
|
05-Aug-16 JDB Renamed the P register from "PC" to "PR"
|
|
13-May-16 JDB Modified for revised SCP API function parameter types
|
|
14-Sep-15 JDB Exposed "ipl_edtdelay" via a REG_HIDDEN to allow user tuning
|
|
Corrected typos in comments and strings
|
|
05-Jun-15 JDB Merged 3.x and 4.x versions using conditionals
|
|
11-Feb-15 MP Revised ipl_detach and ipl_dscln for new sim_close_sock API
|
|
30-Dec-14 JDB Added S-register parameters to ibl_copy
|
|
12-Dec-12 MP Revised ipl_attach for new socket API
|
|
25-Oct-12 JDB Removed DEV_NET to allow restoration of listening ports
|
|
09-May-12 JDB Separated assignments from conditional expressions
|
|
10-Feb-12 JDB Deprecated DEVNO in favor of SC
|
|
Added CARD_INDEX casts to dib.card_index
|
|
07-Apr-11 JDB A failed STC may now be retried
|
|
28-Mar-11 JDB Tidied up signal handling
|
|
27-Mar-11 JDB Consolidated reporting of consecutive CRS signals
|
|
29-Oct-10 JDB Revised for new multi-card paradigm
|
|
26-Oct-10 JDB Changed I/O signal handler for revised signal model
|
|
07-Sep-08 JDB Changed Telnet poll to connect immediately after reset or attach
|
|
15-Jul-08 JDB Revised EDT handler to refine completion delay conditions
|
|
09-Jul-08 JDB Revised ipl_boot to use ibl_copy
|
|
26-Jun-08 JDB Rewrote device I/O to model backplane signals
|
|
01-Mar-07 JDB IPLI EDT delays DMA completion interrupt for TSB
|
|
Added debug printouts
|
|
28-Dec-06 JDB Added ioCRS state to I/O decoders
|
|
16-Aug-05 RMS Fixed C++ declaration and cast problems
|
|
07-Oct-04 JDB Fixed enable/disable from either device
|
|
26-Apr-04 RMS Fixed SFS x,C and SFC x,C
|
|
Implemented DMA SRQ (follows FLG)
|
|
21-Dec-03 RMS Adjusted ipl_ptime for TSB (from Mike Gemeny)
|
|
09-May-03 RMS Added network device flag
|
|
31-Jan-03 RMS Links are full duplex (found by Mike Gemeny)
|
|
|
|
References:
|
|
- 12875A Processor Interconnect Kit Operating and Service Manual
|
|
(12875-90002, January 1974)
|
|
- 12566B[-001/2/3] Microcircuit Interface Kits Operating and Service Manual
|
|
(12566-90015, April 1976)
|
|
|
|
The HP 12875A Processor Interconnect kit is used to communicate between the
|
|
System Processor and the I/O Processor of a two-CPU HP 2000 Time-Shared BASIC
|
|
system. The kit consists of four identical 12566A Microcircuit Interfaces
|
|
and two interconnecting cables. One pair of interfaces is installed in
|
|
adjacent I/O slots in each CPU, and the cables are used to connect the
|
|
higher-priority (lower select code) interface in each computer to the
|
|
lower-priority interface in the other computer. This interconnection
|
|
provides a full-duplex 16-bit parallel communication channel between the
|
|
processors. Each interface is actually a bi-directional, half-duplex line
|
|
that is used in the primary direction for commands and in the reverse
|
|
direction for status.
|
|
|
|
Two instances of the HP2100 simulator are run to simulate the SP and IOP.
|
|
Each simulator contains an Inbound Data interface assigned to the
|
|
lower-numbered select code, and an Outbound Data interface assigned to the
|
|
higher-numbered select code. The IPLI and IPLO devices, respectively,
|
|
simulate these interfaces, while the IPL device represents the combination.
|
|
A shared memory area simulates the interconnecting cables.
|
|
|
|
An essential aspect of TSB startup is that the IOP is running before the SP
|
|
attempts to communicate with it. If the IOP is not running or is otherwise
|
|
non-responsive, the SP startup routine halts with an error message. In
|
|
hardware, this is accomplished by having the system operator start the IOP
|
|
processor before the SP processor. In simulation, however, starting the IOP
|
|
instance before the SP instance does not guarantee that it will run
|
|
uninterrupted. In response to system load, the host operating system may
|
|
block or preempt the IOP instance, resulting in a TSB startup failure.
|
|
|
|
The IPL device provides two synchronization event mechanisms to ensure that
|
|
system startup order is preserved, regardless of host system load. The first
|
|
provides simple WAIT and SIGNAL commands that may be placed in command files
|
|
to cause one simulator instance to suspend until signaled by the other
|
|
instance. This cam ensure, for example, that one instance has loaded its
|
|
communication program before the other instance begins to communicate.
|
|
|
|
For finer-grained control, the second mechanism provides an instruction
|
|
interlock. This allows each instance to execute a given number of machine
|
|
instructions before performing a rendezvous with the other instance. With
|
|
this mechanism, process preemption by the host does not allow one instance to
|
|
get ahead of the other instance.
|
|
|
|
Both mechanisms are implemented by host-platform events (semaphores) and may
|
|
be used concurrently, if desired. If events are unsupported, a fallback
|
|
mechanism is employed that uses timed pauses instead of handshakes. Without
|
|
host system synchronization support, the simulated system's OS might work,
|
|
but if a fallback was not available, then the simulator command files running
|
|
on such systems would refuse to run.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "IPL" ("InterProcessor Link") designation is used throughout this
|
|
file for historical reasons, although HP designates this device as the
|
|
Processor Interconnect Kit.
|
|
*/
|
|
|
|
|
|
|
|
#include <signal.h>
|
|
|
|
#include "sim_defs.h"
|
|
#include "sim_timer.h"
|
|
#include "sim_shmem.h"
|
|
|
|
#include "hp2100_defs.h"
|
|
#include "hp2100_io.h"
|
|
|
|
|
|
|
|
/* Process synchronization definitions */
|
|
|
|
|
|
/* Windows process synchronization */
|
|
|
|
#if defined (_WIN32) && ! defined (USE_FALLBACK)
|
|
|
|
#include <windows.h>
|
|
|
|
typedef HANDLE EVENT; /* the event type */
|
|
|
|
#define UNDEFINED_EVENT NULL /* the initial (undefined) event value */
|
|
|
|
|
|
/* UNIX process synchronization */
|
|
|
|
#elif defined (HAVE_SEMAPHORE) && ! defined (USE_FALLBACK)
|
|
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <semaphore.h>
|
|
#include <time.h>
|
|
|
|
typedef sem_t *EVENT; /* the event type */
|
|
|
|
#define UNDEFINED_EVENT SEM_FAILED /* the initial (undefined) event value */
|
|
#define INFINITE 2000000 /* an "infinite" timeout period (in msec, about 33 minutes) */
|
|
|
|
|
|
/* Process synchronization stub */
|
|
|
|
#else
|
|
|
|
typedef uint32 EVENT; /* the event type */
|
|
|
|
#define UNDEFINED_EVENT 0 /* the initial (undefined) event value */
|
|
#define INFINITE 2000000 /* an "infinite" timeout period (in msec, about 33 minutes) */
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define CARD_COUNT 2 /* count of cards supported */
|
|
#define DATA_MASK 0177u /* characters use only 7 bits for data */
|
|
|
|
|
|
/* ATTACH mode switches */
|
|
|
|
#define SP SWMASK ('S') /* SP switch */
|
|
#define IOP SWMASK ('I') /* IOP switch */
|
|
|
|
#define LISTEN SWMASK ('L') /* listen switch (deprecated) */
|
|
#define CONNECT SWMASK ('C') /* connect switch (deprecated) */
|
|
|
|
|
|
/* Per-unit state variables */
|
|
|
|
#define ID u3 /* session identifying number */
|
|
|
|
|
|
/* Unit flags */
|
|
|
|
#define UNIT_DIAG_SHIFT (UNIT_V_UF + 0) /* diagnostic mode */
|
|
|
|
#define UNIT_DIAG (1u << UNIT_DIAG_SHIFT)
|
|
|
|
|
|
/* Unit references */
|
|
|
|
typedef enum {
|
|
ipli, /* inbound card index */
|
|
iplo /* outbound card index */
|
|
} CARD_INDEX;
|
|
|
|
#define poll_unit ipl_unit [ipli] /* inbound card unit (poll unit) */
|
|
#define sync_unit ipl_unit [iplo] /* outbound card unit (synchronization unit) */
|
|
|
|
|
|
/* Device information block references */
|
|
|
|
#define ipli_dib ipl_dib [ipli] /* inbound card DIB */
|
|
#define iplo_dib ipl_dib [iplo] /* outbound card DIB */
|
|
|
|
|
|
/* Command accessors.
|
|
|
|
Commands are issued from the SP to the IOP to inform the latter of changes in
|
|
the operating system state and to request terminal services. In some cases,
|
|
the IOP responds with status to indicate whether or not the command was
|
|
successful. In a few cases, the IOP responds with a block of data that is
|
|
transferred via DMA. The IOP can send a few commands of its own to the SP
|
|
that reflect availability of terminal data.
|
|
|
|
Commands are sent on the outbound side of the output channel, and status is
|
|
received on the inbound side. Commands are received on the inbound side of
|
|
the input channel, and status is returned on the outbound side.
|
|
|
|
System Processor commands are encoded in 16-bit words, with an opcode
|
|
designating the command in bits 15-13 and additional information in the
|
|
remaining bits, as follows:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| opcode | - - - - - | unsigned integer | form 1
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| opcode | port number | ASCII character | form 2
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| opcode | port number | unsigned integer | form 3
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| opcode | device number | unsigned integer | form 4
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Opcode 7 (and, additionally for 2000 Access, opcode 6) uses an additional
|
|
five bits as a subopcode to determine the command, as follows:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 x | - - - - - - - - | subopcode | form 5
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 x | port number | - - - | subopcode | form 6
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 x | device number | - - | subopcode | form 7
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 x | word count | - - | subopcode | form 8
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Finally, opcode 7 subopcode 0 uses three extra bits to extend command
|
|
decoding to a third level, as follows:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 1 | - - - | extension | - - | 0 0 0 0 0 | form 9
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The commands differ from TSB version to version and are not proper subsets:
|
|
|
|
Op Sub Ext 2000 Access Command 2000F Command 2000B/C Command
|
|
-- --- --- ------------------------- ----------------------------- -----------------------------
|
|
0 - POC Process output character OCR Process output character OCR Process output character
|
|
1 - STE Start ENTER timing STE Start ENTER timing STE Start ENTER timing
|
|
2 - STP Subtype information GTC Fetch next character GTC Fetch next character
|
|
3 - PHS Phones timing parameter PHO Phones timing PHO Phones timing
|
|
4 - PCF Perform control function SPE Baud rate info SPE Baud rate info
|
|
5 - POS Process output string SBP Save buffer pointer SBP Save buffer pointer
|
|
6 00 SBL Send buffer length RBP Restore buffer pointer RBP Restore buffer pointer
|
|
6 01 WTP What terminal type -- --
|
|
7 00 0 INI Initialize IOP INI Initialize IOP INI Initialize IOP
|
|
7 00 1 KSN Cold dump request -- --
|
|
7 00 2 SNP Send number of ports -- --
|
|
7 00 3 SDT Send device table -- --
|
|
7 00 4 SSD System shut down -- --
|
|
7 00 5 SDC Send date code -- --
|
|
7 00 6 --- (unused) -- --
|
|
7 00 7 --- (unused) -- --
|
|
|
|
7 01 UIR User is running UIR User is running UIR User is running
|
|
7 02 UNR User not running UNR User not running UNR User not running
|
|
7 03 IWT Input wait IWT Input wait IWT Input wait
|
|
7 04 HUU Hang user up HUU Hang user up HUU Hang user up
|
|
7 05 ULO User logged on ULO User logged on ULO User logged on
|
|
7 06 ECO Echo-on ECO Echo-on ECO Echo-on
|
|
7 07 ECF Echo-off ECF Echo-off ECF Echo-off
|
|
7 10 TPO Tape mode TPO Tape mode on TPO Tape mode on
|
|
7 11 STR Start timed retries ILI Illegal input ILI Illegal input
|
|
7 12 NUC New user called NUC New user called NUC New user called
|
|
7 13 KTO Kill terminal output KTO Kill terminal output KTO Kill terminal output
|
|
7 14 ALI Allow input ALI Allow input ALI Allow input
|
|
7 15 OWT Output wait OWT Output wait OWT Output wait
|
|
7 16 IBA Is buffer available IBF Is buffer full IBF Is buffer full
|
|
7 17 ADV Allocate device PSC Line printer select code PSC Line printer select code
|
|
7 20 RDV Release device LPR Line printer request LPR Line printer request
|
|
7 21 ALB Allocate buffer LPD Line printer disconnect LPD Line printer disconnect
|
|
7 22 XRB Transfer input buffer LPS Line printer status LPS Line printer status
|
|
7 23 BKS Backspace terminal buffer BKS Backspace terminal buffer BKS Backspace terminal buffer
|
|
7 24 KDO Kill device output CHS Character size CHS Character size
|
|
7 25 FNC Fetch next character STP Subtype info STP Subtype info
|
|
7 26 RJE RJE command GRP Get receive parameter WSP What baud rate
|
|
7 27 ABT User is being aborted ABT User is being aborted WCS What character size
|
|
7 30 PIS Process input string WTP What terminal type WTP What terminal type
|
|
7 31 --- (unused) KSN Send core image TKO Dump to line printer
|
|
7 32 SCI Send core image --- (unused) ABT User is being aborted
|
|
7 33 RLB Release buffer --- (unused) --- (unused)
|
|
7 34 SSD System shutdown --- (unused) --- (unused)
|
|
7 35 SBP Save buffer pointer --- (unused) --- (unused)
|
|
7 36 RBP Restore buffer pointer --- (unused) --- (unused)
|
|
7 37 TCM Transmit console message --- (unused) --- (unused)
|
|
|
|
When the IOP returns a status word, it is encoded as follows:
|
|
|
|
Response Meaning
|
|
-------- ------------------------
|
|
-3 No data available on RJE
|
|
-2 End of file
|
|
-1 Buffer not ready
|
|
0 Operation successful
|
|
1 Device not ready
|
|
2 Device error
|
|
3 Attention needed
|
|
4 Read/write failure
|
|
|
|
|
|
|
|
I/O Processor commands are encoded in 16-bit words, with an opcode
|
|
designating the command in bits 15-13 and additional information in the
|
|
remaining bits, as follows:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| opcode | port number | unsigned integer | form 1
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
Only 2000 Access uses opcode 7 as a subopcode indicator, with these forms:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 1 | - - - - - - - - - | subopcode | form 2
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 1 | port number | - - - - | subopcode | form 3
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 1 | device number | - - - | subopcode | form 4
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| 1 1 1 | word count | - - - | subopcode | form 5
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
|
The commands differ from TSB version to version and are not proper subsets:
|
|
|
|
Op Sub 2000 Access Command 2000F Command 2000B/C Command
|
|
-- --- -------------------------------- ------------------- -------------------
|
|
0 - HVL Have a line HVL Have line HVL Have line
|
|
1 - HVP Have a line - parity error ABR User aborted ABR User aborted
|
|
2 - HLL Have a line - lost character BFL Buffer full BFL Buffer full
|
|
3 - --- (unused) BFE Buffer empty BFE Buffer empty
|
|
4 - --- (unused) ETO ENTER timed out ETO ENTER timed out
|
|
5 - --- (unused) UHU User hung up UHU User hung up
|
|
6 - --- (unused) --- (unused) --- (unused)
|
|
7 00 ABR User abort request --- (unused) --- (unused)
|
|
7 01 BFL Buffer full --- (unused) --- (unused)
|
|
7 02 BFE Buffer empty --- (unused) --- (unused)
|
|
7 03 ETO ENTER timed out --- (unused) --- (unused)
|
|
7 04 UHU User hung up --- (unused) --- (unused)
|
|
7 05 SCM Send console message --- (unused) --- (unused)
|
|
7 06 ADR Allocate device for RJE --- (unused) --- (unused)
|
|
7 07 RDR Release device from RJE --- (unused) --- (unused)
|
|
7 10 WUU Wake user up --- (unused) --- (unused)
|
|
7 11 WRU Wake RJE up --- (unused) --- (unused)
|
|
7 12 --- (unused) --- (unused) --- (unused)
|
|
7 13 --- (unused) --- (unused) --- (unused)
|
|
7 14 --- (unused) --- (unused) --- (unused)
|
|
7 15 --- (unused) --- (unused) --- (unused)
|
|
7 16 --- (unused) --- (unused) --- (unused)
|
|
7 17 --- (unused) --- (unused) --- (unused)
|
|
*/
|
|
|
|
#define CM_OPCODE_MASK 0160000u /* operation code mask */
|
|
#define CM_EXTOP_MASK 0001600u /* extended opcode mask */
|
|
#define CM_SUBOP_MASK 0000037u /* subopcode mask */
|
|
|
|
#define CM_OPCODE_SHIFT 13
|
|
#define CM_EXTOP_SHIFT 7
|
|
#define CM_SUBOP_SHIFT 0
|
|
|
|
#define CM_OPCODE(c) (((c) & CM_OPCODE_MASK) >> CM_OPCODE_SHIFT)
|
|
#define CM_EXTOP(c) (((c) & CM_EXTOP_MASK) >> CM_EXTOP_SHIFT)
|
|
#define CM_SUBOP(c) (((c) & CM_SUBOP_MASK) >> CM_SUBOP_SHIFT)
|
|
|
|
|
|
/* Command decoding.
|
|
|
|
Some SP commands receive status or data back from the IOP in response, though
|
|
most do not. Additionally, some commands supply parameters or data to the
|
|
IOP. To decode and trace those commands successfully, the state of the
|
|
command exchange must be tracked. After sending the command opcode, there
|
|
are 12 different exchanges that follow, as enumerated below:
|
|
|
|
1. None
|
|
2. Character received
|
|
3. Binary data received
|
|
4. DMA packed characters received
|
|
5. DMA binary data received
|
|
6. Status received
|
|
7. Status received + DMA packed characters sent
|
|
8. Status received + Binary data received + DMA packed characters received
|
|
9. Binary data received + DMA packed characters received
|
|
10. Binary data sent + DMA binary data received
|
|
11. Binary data sent + Status received
|
|
12. Binary data sent + Status received + DMA packed characters sent
|
|
|
|
States are defined that encode each of possible exchanges. For trace message
|
|
clarity, separate states are provided for received decimal and octal binary
|
|
data. The trace routines set the next state from the entry state, as
|
|
follows:
|
|
|
|
Entry State Next State Response
|
|
------------- ------------ --------
|
|
None None 1
|
|
Character None 2
|
|
Decimal None 3
|
|
Octal None 3
|
|
DMA_Chars DMA_Chars 4
|
|
DMA_Octal DMA_Octal 5
|
|
Status None 6
|
|
Status_DMAC DMA_Chars 7
|
|
Stat_Dec_DMAC Decimal_DMAC 8
|
|
Decimal_DMAC DMA_Chars 9
|
|
Octal_DMAB DMA_Octal 10
|
|
Dec_Status Status 11
|
|
Dec_Stat_DMAC Status_DMAC 12
|
|
|
|
All of the IOP-to-SP commands are opcode-only, except for the "Allocate
|
|
device for RJE" command, which returns a status word.
|
|
*/
|
|
|
|
typedef enum { /* response states */
|
|
None, /* None */
|
|
Character, /* Character in */
|
|
Decimal, /* Decimal data in */
|
|
Octal, /* Octal data in */
|
|
Status, /* DMA packed characters in */
|
|
DMA_Chars, /* DMA binary data in */
|
|
DMA_Octal, /* Status in */
|
|
Status_DMAC, /* Status in + DMA packed characters out */
|
|
Stat_Dec_DMAC, /* Status in + Binary data in + DMA packed characters in */
|
|
Decimal_DMAC, /* Decimal data in + DMA packed characters in */
|
|
Octal_DMAB, /* Octal data out + DMA binary data in */
|
|
Dec_Status, /* Decimal data out + Status in */
|
|
Dec_Stat_DMAC /* Decimal data out + Status in + DMA packed characters out */
|
|
} RESPONSE;
|
|
|
|
|
|
/* Command descriptor table.
|
|
|
|
Command decoding is driven by a table whose entries describe the command
|
|
format. As mentioned above, command words contain an opcode field, an
|
|
optional port or device number field, and an optional subopcode field. Table
|
|
entries describe how to separate and label the fields, as well as what
|
|
responses are expected.
|
|
|
|
A mask field describes how to isolate the first operand (port or device
|
|
number). By implication, if there is a second operand, it fills the
|
|
remaining bits of the word. The presence of the operands are indicated by
|
|
the presence of operand labels -- an empty string indicates that the
|
|
corresponding operand is not present. An entry with a NULL command name is
|
|
not assigned.
|
|
|
|
The table is organized into several sections. The first and largest section
|
|
contains entries for the 2000 Access SP commands. Entries 0-6 correspond
|
|
directly to opcodes 0-6. Entry 7 corresponds to opcode 7 and is unused.
|
|
Entries 10-47 (octal) correspond to opcode 7, subopcodes 0-37 (octal).
|
|
|
|
The second section contains entries for the 2000 Access opcode 7 subopcode 0
|
|
extensions. Entries 50-57 (octal) correspond to extension codes 0-7.
|
|
|
|
Section three corresponds to the 2000 Access IOP commands. Entries 60-62
|
|
(octal) correspond to opcodes 0-2. Entries 63-67 are unused, and entries
|
|
70-101 (octal) correspond to opcode 7 subopcodes 0-11 (octal).
|
|
|
|
The SP commands unique to 2000F appear in section four. They do not
|
|
correspond directly to any opcode/subopcode pattern but instead are
|
|
referenced by the 2000F remapping table. Consequently, the order is
|
|
irrelevant.
|
|
|
|
As the unique 2000F IOP commands are fully implemented in 2000 Access but
|
|
with different code points, a section five is not needed. Remapping the
|
|
2000F IOP codes to their counterparts in the Access code space is sufficient.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Access defines two SP opcodes as subopcode groups. Opcode 7 introduces a
|
|
subopcode group in both Access and F, while opcode 6 is a subopcode group
|
|
only in Access. However, the group has only two members, and one of them
|
|
(opcode 6 subopcode 0, "Send buffer length") isn't actually used by the
|
|
SP. So Entry 6 in the table corresponds to opcode 6 subopcode 1.
|
|
*/
|
|
|
|
#define SUBOP_OPCODE 007 /* opcode for subopcode commands */
|
|
#define SUBOP_OFFSET 010 /* table index offset of subopcode commands */
|
|
#define EXTOP_OFFSET 040 /* table index offset of extended opcode commands */
|
|
#define IOP_OFFSET 060 /* table index offset of IOP commands */
|
|
|
|
typedef struct { /* command descriptor */
|
|
RESPONSE response; /* response format */
|
|
uint32 mask; /* operand mask */
|
|
char *high_label; /* first operand label */
|
|
char *low_label; /* second operand label */
|
|
char *name; /* command name */
|
|
} DESCRIPTOR;
|
|
|
|
static DESCRIPTOR cmd [] = {
|
|
/* Response Mask High Label Low Label Command Name Index + [sub]opcode */
|
|
/* ------------- ------ ---------- ------------- --------------------------- ------------------- */
|
|
|
|
/* 2000 Access SP primary entries */
|
|
|
|
{ None, 017400, " port ", " character ", "Process output character" }, /* 000 + 00 */
|
|
{ None, 017400, " port ", " seconds ", "Start ENTER timing" }, /* 000 + 01 */
|
|
{ None, 017400, " port ", " type code ", "Subtype information" }, /* 000 + 02 */
|
|
{ None, 000000, "", " seconds ", "Phones timing" }, /* 000 + 03 */
|
|
{ Status, 017600, " device ", " control ", "Perform control function" }, /* 000 + 04 */
|
|
{ Status_DMAC, 017400, " port ", " count ", "Process output string" }, /* 000 + 05 */
|
|
{ Decimal, 017400, " port ", "", "What terminal type" }, /* 000 + 06 */
|
|
{ None, 000000, "", "", NULL }, /* 000 + 07 */
|
|
|
|
/* 2000 Access SP secondary entries */
|
|
|
|
{ None, 017400, " count ", "", "Initialize IOP" }, /* 010 + 00 */
|
|
{ None, 017400, " port ", "", "User is running" }, /* 010 + 01 */
|
|
{ None, 017400, " port ", "", "User not running" }, /* 010 + 02 */
|
|
{ None, 017400, " port ", "", "Input wait" }, /* 010 + 03 */
|
|
{ None, 017400, " port ", "", "Hang user up" }, /* 010 + 04 */
|
|
{ None, 017400, " port ", "", "User logged on" }, /* 010 + 05 */
|
|
{ None, 017400, " port ", "", "Echo on" }, /* 010 + 06 */
|
|
{ None, 017400, " port ", "", "Echo off" }, /* 010 + 07 */
|
|
{ None, 017400, " port ", "", "Tape mode on" }, /* 010 + 10 */
|
|
{ None, 017600, " device ", "", "Start timed retries" }, /* 010 + 11 */
|
|
{ None, 017400, " port ", "", "New user called" }, /* 010 + 12 */
|
|
{ None, 017400, " port ", "", "Kill terminal output" }, /* 010 + 13 */
|
|
{ None, 017400, " port ", "", "Allow input" }, /* 010 + 14 */
|
|
{ None, 017400, " port ", "", "Output wait" }, /* 010 + 15 */
|
|
{ Status, 017400, " port ", "", "Is buffer available" }, /* 010 + 16 */
|
|
{ Dec_Status, 017600, " device ", "", "Allocate device" }, /* 010 + 17 */
|
|
{ Status, 017600, " device ", "", "Release device" }, /* 010 + 20 */
|
|
{ Dec_Stat_DMAC, 017600, " device ", "", "Allocate buffer" }, /* 010 + 21 */
|
|
{ Stat_Dec_DMAC, 017600, " device ", "", "Transfer input buffer" }, /* 010 + 22 */
|
|
{ None, 017400, " port ", "", "Backspace terminal buffer" }, /* 010 + 23 */
|
|
{ None, 017600, " device ", "", "Kill device output" }, /* 010 + 24 */
|
|
{ Character, 017400, " port ", "", "Fetch next character" }, /* 010 + 25 */
|
|
{ Status_DMAC, 017600, " count ", "", "RJE command" }, /* 010 + 26 */
|
|
{ None, 017400, " port ", "", "User is being aborted" }, /* 010 + 27 */
|
|
{ Dec_Stat_DMAC, 017400, " port ", "", "Process input string" }, /* 010 + 30 */
|
|
{ None, 000000, "", "", NULL }, /* 010 + 31 */
|
|
{ Octal_DMAB, 017600, " count ", "", "Send core image" }, /* 010 + 32 */
|
|
{ None, 017400, " port ", "", "Release buffer" }, /* 010 + 33 */
|
|
{ None, 000000, "", "", "System shutdown" }, /* 010 + 34 */
|
|
{ None, 017400, " port ", "", "Save buffer pointer" }, /* 010 + 35 */
|
|
{ None, 017400, " port ", "", "Restore buffer pointer" }, /* 010 + 36 */
|
|
{ DMA_Chars, 017400, " port ", "", "Transmit console message" }, /* 010 + 37 */
|
|
|
|
/* 2000 Access SP extension entries */
|
|
|
|
{ Decimal, 000000, "", "", "Initialize IOP" }, /* 010 + 40 */
|
|
{ None, 000000, "", "", "Cold dump request" }, /* 010 + 41 */
|
|
{ Decimal, 000000, "", "", "Send number of ports" }, /* 010 + 42 */
|
|
{ DMA_Octal, 000000, "", "", "Send device table" }, /* 010 + 43 */
|
|
{ None, 000000, "", "", "System shut down" }, /* 010 + 44 */
|
|
{ Decimal, 000000, "", "", "Send date code" }, /* 010 + 45 */
|
|
{ None, 000000, "", "", NULL }, /* 010 + 46 */
|
|
{ None, 000000, "", "", NULL }, /* 010 + 47 */
|
|
|
|
/* 2000 Access IOP primary entries */
|
|
|
|
{ None, 017400, " port ", " seconds ", "Have a line" }, /* 060 + 00 */
|
|
{ None, 017400, " port ", " seconds ", "Have a line (parity)" }, /* 060 + 01 */
|
|
{ None, 017400, " port ", " seconds ", "Have a line (lost)" }, /* 060 + 02 */
|
|
{ None, 000000, "", "", NULL }, /* 060 + 03 */
|
|
{ None, 000000, "", "", NULL }, /* 060 + 04 */
|
|
{ None, 000000, "", "", NULL }, /* 060 + 05 */
|
|
{ None, 000000, "", "", NULL }, /* 060 + 06 */
|
|
{ None, 000000, "", "", NULL }, /* 060 + 07 */
|
|
|
|
/* 2000 Access IOP secondary entries */
|
|
|
|
{ None, 017400, " port ", "", "User abort request" }, /* 070 + 00 */
|
|
{ None, 017400, " port ", "", "Buffer full" }, /* 070 + 01 */
|
|
{ None, 017400, " port ", "", "Buffer empty" }, /* 070 + 02 */
|
|
{ None, 017400, " port ", "", "ENTER timed out" }, /* 070 + 03 */
|
|
{ None, 017400, " port ", "", "User hung up" }, /* 070 + 04 */
|
|
{ None, 017600, " count ", "", "Send console message" }, /* 070 + 05 */
|
|
{ Status, 017600, " device ", "", "Allocate device for RJE" }, /* 070 + 06 */
|
|
{ None, 017600, " device ", "", "Release device from RJE" }, /* 070 + 07 */
|
|
{ None, 017600, " device ", "", "Wake user up" }, /* 070 + 10 */
|
|
{ None, 000000, "", "", "Wake RJE up" }, /* 070 + 11 */
|
|
{ None, 000000, "", "", NULL }, /* 070 + 12 */
|
|
{ None, 000000, "", "", NULL }, /* 070 + 13 */
|
|
{ None, 000000, "", "", NULL }, /* 070 + 14 */
|
|
{ None, 000000, "", "", NULL }, /* 070 + 15 */
|
|
{ None, 000000, "", "", NULL }, /* 070 + 16 */
|
|
{ None, 000000, "", "", NULL }, /* 070 + 17 */
|
|
|
|
/* 2000F SP remapping entries */
|
|
|
|
{ None, 017400, " port ", " rate code ", "Baud rate" }, /* 110 + 00 */
|
|
{ Status, 017400, " port ", "", "Illegal input" }, /* 110 + 01 */
|
|
{ Status, 017400, " port ", "", "Is buffer full" }, /* 110 + 02 */
|
|
{ None, 017600, " device ", "", "Line printer select code" }, /* 110 + 03 */
|
|
{ Octal, 017400, " port ", "", "Line printer request" }, /* 110 + 04 */
|
|
{ None, 000000, "", "", "Line printer disconnect" }, /* 110 + 05 */
|
|
{ Octal, 000000, "", "", "Line printer status" }, /* 110 + 06 */
|
|
{ None, 017400, " port ", "", "Character size" }, /* 110 + 07 */
|
|
{ None, 017400, " port ", "", "Subtype info" }, /* 110 + 10 */
|
|
{ Octal, 017400, " port ", "", "Get receive parameter" }, /* 110 + 11 */
|
|
{ Octal, 017400, " port ", "", "What terminal type" } /* 110 + 12 */
|
|
};
|
|
|
|
static const uint32 remap_2000F [] = { /* remap from 2000 Access to 2000F opcodes */
|
|
0000, 0001, 0035, 0003, 0110, 0045, 0046, 0007, /* SP remapping entries 000-007 */
|
|
0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017, /* SP remapping entries 010-017 */
|
|
0020, 0111, 0022, 0023, 0024, 0025, 0112, 0113, /* SP remapping entries 020-027 */
|
|
0114, 0115, 0116, 0033, 0117, 0120, 0121, 0037, /* SP remapping entries 030-037 */
|
|
0122, 0042, 0007, 0007, 0007, 0007, 0007, 0007, /* SP remapping entries 040-047 */
|
|
0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000, /* SP remapping entries 050-057 */
|
|
|
|
0060, 0070, 0071, 0072, 0073, 0074, 0066, 0067 /* IOP remapping entries 060-067 */
|
|
};
|
|
|
|
|
|
#define STATUS_BIAS 3 /* bias for using status as an index */
|
|
|
|
static char *status_names [] = { /* names for the status returns */
|
|
"No data available on RJE or LT", /* -3 */
|
|
"End of file", /* -2 */
|
|
"Buffer not ready", /* -1 */
|
|
"Operation successful", /* 0 */
|
|
"Device not ready", /* 1 */
|
|
"Device error", /* 2 */
|
|
"Attention needed", /* 3 */
|
|
"Read/write failure" /* 4 */
|
|
};
|
|
|
|
|
|
/* IPL card state */
|
|
|
|
typedef struct {
|
|
HP_WORD output_word; /* output word register */
|
|
HP_WORD input_word; /* input word register */
|
|
FLIP_FLOP command; /* command flip-flop */
|
|
FLIP_FLOP control; /* control flip-flop */
|
|
FLIP_FLOP flag; /* flag flip-flop */
|
|
FLIP_FLOP flag_buffer; /* flag buffer flip-flop */
|
|
} CARD_STATE;
|
|
|
|
static CARD_STATE ipl [CARD_COUNT]; /* per-card state */
|
|
|
|
|
|
/* IPL I/O device state.
|
|
|
|
The 12566B Microcircuit Interface provides a 16-bit Data Out bus and a 16-bit
|
|
Data In bus, as well as an outbound Device Command signal and an inbound
|
|
Device Flag signal to indicate data availability. The output and input
|
|
states are modelled by a pair of structures that also contain Boolean flags
|
|
to indicate cable connectivity.
|
|
|
|
The two interface cards provided each may be connected in one of four
|
|
possible ways:
|
|
|
|
1. No connection (the I/O cable is not connected).
|
|
|
|
2. Loopback connection (a loopback connector is in place).
|
|
|
|
3. Cross connection (an I/O cable connects one card to the other card in the
|
|
same machine).
|
|
|
|
4. Processor interconnection (an I/O cable connects a card in one machine to
|
|
a card in the other machine).
|
|
|
|
In simulation, these four connection states are modelled by setting input and
|
|
output pointers (accessors) to point at the appropriate state structures, as
|
|
follows:
|
|
|
|
1. The input and output accessors point at separate local input and output
|
|
state structures.
|
|
|
|
2. The input and output accessors point at a single local state structure.
|
|
|
|
3. The input and output accessors of one card point at the separate local
|
|
state structures of the other card.
|
|
|
|
4. The input and output accessors of one card point at the separate shared
|
|
state structures of the other card.
|
|
|
|
Connection is accomplished by having an output accessor and an input accessor
|
|
point at the same state structure. Graphically, the four possibilities are:
|
|
|
|
1. No connection:
|
|
|
|
+------------------+
|
|
card [n].output --> | Data Out |
|
|
+------------------+
|
|
| Device Command |
|
|
+------------------+
|
|
|
|
+------------------+
|
|
card [n].input --> | Data In |
|
|
+------------------+
|
|
| Device Flag |
|
|
+------------------+
|
|
|
|
|
|
2. Loopback connection:
|
|
|
|
+------------------+------------------+
|
|
card [n].output --> | Data Out | Data In | <-- card [n].input
|
|
+------------------+------------------+
|
|
| Device Command | Device Flag |
|
|
+------------------+------------------+
|
|
|
|
|
|
3. Cross connection:
|
|
|
|
+------------------+------------------+
|
|
card [0].output --> | Data Out | Data In | <-- card [1].input
|
|
+------------------+------------------+
|
|
| Device Command | Device Flag |
|
|
+------------------+------------------+
|
|
|
|
+------------------+------------------+
|
|
card [0].input --> | Data In | Data Out | <-- card [1].output
|
|
+------------------+------------------+
|
|
| Device Flag | Device Command |
|
|
+------------------+------------------+
|
|
|
|
|
|
4. Processor interconnection:
|
|
|
|
+------------------+------------------+
|
|
card [0].output --> | Data Out | Data In | <-- card [1].input
|
|
+------------------+------------------+
|
|
| Device Command | Device Flag |
|
|
+------------------+------------------+
|
|
|
|
+------------------+------------------+
|
|
card [0].input --> | Data In | Data Out | <-- card [1].output
|
|
+------------------+------------------+
|
|
| Device Flag | Device Command |
|
|
+------------------+------------------+
|
|
|
|
+------------------+------------------+
|
|
card [1].output --> | Data Out | Data In | <-- card [0].input
|
|
+------------------+------------------+
|
|
| Device Command | Device Flag |
|
|
+------------------+------------------+
|
|
|
|
+------------------+------------------+
|
|
card [1].input --> | Data In | Data Out | <-- card [0].output
|
|
+------------------+------------------+
|
|
| Device Flag | Device Command |
|
|
+------------------+------------------+
|
|
|
|
In all but case 1, two accessors point at the same structure but with
|
|
different views.
|
|
*/
|
|
|
|
typedef struct {
|
|
t_bool cable_connected; /* TRUE if the inbound cable is connected */
|
|
t_bool device_flag_in; /* external DEVICE FLAG signal state */
|
|
HP_WORD data_in; /* external DATA IN signal bus */
|
|
} INPUT_STATE, *INPUT_STATE_PTR;
|
|
|
|
typedef struct {
|
|
t_bool cable_connected; /* TRUE if the outbound cable is connected */
|
|
t_bool device_command_out; /* external DEVICE COMMAND signal state */
|
|
HP_WORD data_out; /* external DATA OUT signal bus */
|
|
} OUTPUT_STATE, *OUTPUT_STATE_PTR;
|
|
|
|
typedef struct { /* the normal ("forward direction") state view */
|
|
INPUT_STATE input;
|
|
OUTPUT_STATE output;
|
|
} FORWARD_STATE;
|
|
|
|
typedef struct { /* the cross-connected ("reverse direction") state view */
|
|
OUTPUT_STATE output;
|
|
INPUT_STATE input;
|
|
} REVERSE_STATE;
|
|
|
|
typedef union { /* the state may be accessed in either direction */
|
|
FORWARD_STATE forward;
|
|
REVERSE_STATE reverse;
|
|
} IO_STATE, *IO_STATE_PTR;
|
|
|
|
typedef struct {
|
|
INPUT_STATE_PTR input; /* the input accessor */
|
|
OUTPUT_STATE_PTR output; /* the output accessor */
|
|
} STATE_PTRS;
|
|
|
|
|
|
/* IPL synchronizer states.
|
|
|
|
One mechanism that synchronizes two simulator instances is implemented by an
|
|
interlocked gate that is in one of three basic states: Locked, Unlocking, or
|
|
Unlocked. When operating in the synchronous mode, each instance schedules
|
|
the IPLO unit to rendezvous with the other instance after a preset number of
|
|
machine instructions have been executed. The first instance that arrives at
|
|
the gate locks it and then waits in a loop for the other unit to arrive.
|
|
When the other instance arrives, it begins unlocking the gate but waits for
|
|
an acknowledgement from the first instance before leaving the gate unlocked.
|
|
Then both instances reschedule their respective IPLO units, and the process
|
|
repeats.
|
|
|
|
To allow efficient implementation, the wait loop starts out compute-bound but
|
|
then shifts to event waits after a short time. The shift is noted by a state
|
|
change from Locked to Locked_Wait (or Unlocking to Unlocking_Wait). This is
|
|
required because the event waits must be signaled but the compute waits must
|
|
not.
|
|
|
|
The allowed state transitions are:
|
|
|
|
Transition Cause
|
|
--------------------------- -----------------------------------------
|
|
Unlocked -> Locked First instance arrives at the rendezvous
|
|
Locked -> Locked_Wait Second instance is slow to arrive
|
|
Locked -> Unlocking Second instance arrives at the rendezvous
|
|
Locked -> Unlocked Abort before second instance arrives
|
|
Locked_Wait -> Unlocking Second instance arrives at the rendezvous
|
|
Locked_Wait -> Unlocked Abort before second instance arrives
|
|
Unlocking -> Unlocking_Wait First instance is slow to confirm
|
|
Unlocking -> Unlocked First instance confirms resumption
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The Wait states must be +1 numerically from the corresponding non-wait
|
|
states.
|
|
*/
|
|
|
|
typedef enum { /* the CPU execution interlock state */
|
|
Unlocked, /* the gate is unlocked */
|
|
Unlocking, /* the gate is unlocking */
|
|
Unlocking_Wait, /* the gate is unlocking and waiting */
|
|
Locked, /* the gate is locked */
|
|
Locked_Wait /* the gate is locked and waiting */
|
|
} GATE_STATE;
|
|
|
|
static const char *gate_state_names [] = { /* gate state names corresponding to GATE_STATE */
|
|
"Unlocked",
|
|
"Unlocking",
|
|
"Unlocking and waiting",
|
|
"Locked",
|
|
"Locked and waiting" };
|
|
|
|
|
|
/* IPL shared memory region */
|
|
|
|
typedef enum { /* the version of Time-Shared BASIC currently running */
|
|
HP_2000BC, /* HP 2000B, C, or C' (C-prime) */
|
|
HP_2000F, /* HP 2000F */
|
|
HP_2000_Access /* HP 2000 Access */
|
|
} OS_VERSION;
|
|
|
|
typedef struct { /* the shared memory region */
|
|
GATE_STATE gate; /* the state of the CPU interlock gate */
|
|
uint32 count; /* the count of instructions to execute before rendezvous */
|
|
OS_VERSION tsb_version; /* the version of TSB that is running */
|
|
IO_STATE dev_bus [CARD_COUNT]; /* the IPL I/O device state */
|
|
} SHARED_REGION;
|
|
|
|
|
|
/* IPL interface state */
|
|
|
|
static t_bool cpu_is_iop = FALSE; /* TRUE if this is the IOP instance, FALSE if SP instance */
|
|
static int32 poll_wait = 50; /* maximum poll wait time (in event ticks) */
|
|
static int32 edt_delay = 0; /* EDT delay (in milliseconds) */
|
|
static int32 fallback_wait = 2; /* sleep time if semaphores are not supported (in seconds) */
|
|
|
|
static char event_name [PATH_MAX]; /* the event name; last character specifies which event */
|
|
static uint32 event_error = 0; /* the host OS error code from a failed process sync call */
|
|
static t_bool wait_aborted = FALSE; /* TRUE if the user aborted a SET IPL WAIT command */
|
|
static EVENT sync_id = UNDEFINED_EVENT; /* the synchronization wait event */
|
|
static EVENT lock_id = UNDEFINED_EVENT; /* the lock wait event */
|
|
static EVENT unlock_id = UNDEFINED_EVENT; /* the unlock wait event */
|
|
|
|
static SHMEM *shared_id; /* a pointer to the shared memory identifier */
|
|
static SHARED_REGION local_region; /* the local I/O device state */
|
|
static SHARED_REGION *shared_ptr; /* a pointer to the shared I/O device state */
|
|
|
|
static uint32 sync_avg = 0; /* average interlock wait time */
|
|
static uint32 sync_max = 0; /* maximum interlock wait time */
|
|
static uint32 sync_cnt = 0; /* count of interlock calls */
|
|
static float sync_mean = 0.0; /* running average interlock wait time */
|
|
|
|
static STATE_PTRS io_ptrs [CARD_COUNT] = { /* the card accessors pointing at the local state */
|
|
{ &local_region.dev_bus [ipli].forward.input, /* card [0].input */
|
|
&local_region.dev_bus [ipli].forward.output }, /* card [0].output */
|
|
|
|
{ &local_region.dev_bus [iplo].forward.input, /* card [1].input */
|
|
&local_region.dev_bus [iplo].forward.output } /* card [1].output */
|
|
};
|
|
|
|
|
|
/* IPL I/O interface routines */
|
|
|
|
static INTERFACE ipl_interface;
|
|
|
|
|
|
/* IPL interface local SCP support routines */
|
|
|
|
static t_stat ipl_set_diag (UNIT *uptr, int32 value, char *cptr, void *desc);
|
|
static t_stat ipl_set_sync (UNIT *uptr, int32 value, char *cptr, void *desc);
|
|
static t_stat ipl_show_sync (FILE *st, UNIT *uptr, int32 value, void *desc);
|
|
|
|
|
|
/* IPL device local SCP support routines */
|
|
|
|
static t_stat ipl_reset (DEVICE *dptr);
|
|
static t_stat ipl_attach (UNIT *uptr, char *cptr);
|
|
static t_stat ipl_detach (UNIT *uptr);
|
|
static t_stat ipl_boot (int32 unitno, DEVICE *dptr);
|
|
|
|
|
|
/* IPL device local utility routines */
|
|
|
|
static t_stat card_service (UNIT *uptr);
|
|
static t_stat sync_service (UNIT *uptr);
|
|
|
|
static t_stat wait_at_gate (EVENT event_id, GATE_STATE initial, GATE_STATE final);
|
|
static void release_wait (EVENT event_id, GATE_STATE initial, GATE_STATE final);
|
|
static void activate_unit (UNIT *uptr, int32 wait_time);
|
|
static void wru_handler (int signal);
|
|
static RESPONSE trace_command (CARD_INDEX card, HP_WORD command, RESPONSE response);
|
|
static RESPONSE trace_status (CARD_INDEX card, HP_WORD status, RESPONSE response);
|
|
|
|
|
|
/* Host-specific process synchronization routines */
|
|
|
|
static uint32 create_event (const char *name, EVENT *event);
|
|
static uint32 destroy_event (const char *name, EVENT *event);
|
|
static uint32 wait_event (EVENT event, uint32 wait_in_ms, t_bool *signaled);
|
|
static uint32 signal_event (EVENT event);
|
|
|
|
|
|
/* IPL SCP data structures */
|
|
|
|
/* Device information blocks */
|
|
|
|
static DIB ipl_dib [CARD_COUNT] = {
|
|
{ &ipl_interface, /* the device's I/O interface function pointer */
|
|
IPLI, /* the device's select code (02-77) */
|
|
0, /* the card index */
|
|
"12875A Processor Interconnect Lower Data PCA", /* the card description */
|
|
"12992K Processor Interconnect Loader" }, /* the ROM description */
|
|
|
|
{ &ipl_interface, /* the device's I/O interface function pointer */
|
|
IPLO, /* the device's select code (02-77) */
|
|
1, /* the card index */
|
|
"12875A Processor Interconnect Upper Data PCA", /* the card description */
|
|
NULL } /* the ROM description */
|
|
};
|
|
|
|
|
|
/* Unit lists */
|
|
|
|
static UNIT ipl_unit [CARD_COUNT] = {
|
|
{ UDATA (&card_service, UNIT_ATTABLE, 0) }, /* the IPLI unit handles I/O for both cards */
|
|
{ UDATA (&sync_service, UNIT_ATTABLE, 0) } /* the IPLO unit handles CPU interlocking */
|
|
};
|
|
|
|
|
|
/* Register lists.
|
|
|
|
Five registers are hidden from the user. The EDTDELAY value sets the number
|
|
of milliseconds to suspend the simulator after an IOP-to-SP data transfer
|
|
completes; see the notes for the "ipl_interface" routine for details. The
|
|
EVTERR value is set to the host system error code if an event operation
|
|
fails. AVG and MAX hold the interlock synchronizer's average and maximum
|
|
loop iteration counts while waiting for the other instance to respond. CNT
|
|
is the total number of interlock calls made. These last three values are
|
|
reset to zero when a RESET -P IPL command is issued.
|
|
*/
|
|
|
|
static REG ipli_reg [] = {
|
|
/* Macro Name Location Width Offset Flags */
|
|
/* ------ -------- ---------------------- ----- ------ -------------------- */
|
|
{ ORDATA (IBUF, ipl [ipli].input_word, 16) },
|
|
{ ORDATA (OBUF, ipl [ipli].output_word, 16) },
|
|
{ FLDATA (CTL, ipl [ipli].control, 0) },
|
|
{ FLDATA (FLG, ipl [ipli].flag, 0) },
|
|
{ FLDATA (FBF, ipl [ipli].flag_buffer, 0) },
|
|
{ DRDATA (TIME, poll_wait, 24), PV_LEFT },
|
|
{ DRDATA (WAIT, fallback_wait, 24), PV_LEFT },
|
|
{ DRDATA (EDTDELAY, edt_delay, 32), PV_LEFT | REG_HIDDEN },
|
|
{ DRDATA (EVTERR, event_error, 32), PV_LEFT | REG_HRO },
|
|
{ DRDATA (AVG, sync_avg, 32), PV_LEFT | REG_HRO },
|
|
{ DRDATA (MAX, sync_max, 32), PV_LEFT | REG_HRO },
|
|
{ DRDATA (CNT, sync_cnt, 32), PV_LEFT | REG_HRO },
|
|
|
|
DIB_REGS (ipli_dib),
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
static REG iplo_reg [] = {
|
|
/* Macro Name Location Width Offset Flags */
|
|
/* ------ -------- ---------------------- ----- ------ -------------------- */
|
|
{ ORDATA (IBUF, ipl [iplo].input_word, 16) },
|
|
{ ORDATA (OBUF, ipl [iplo].output_word, 16) },
|
|
{ FLDATA (CTL, ipl [iplo].control, 0) },
|
|
{ FLDATA (FLG, ipl [iplo].flag, 0) },
|
|
{ FLDATA (FBF, ipl [iplo].flag_buffer, 0) },
|
|
|
|
DIB_REGS (iplo_dib),
|
|
|
|
{ NULL }
|
|
};
|
|
|
|
|
|
/* Modifier lists */
|
|
|
|
typedef enum { /* Synchronization SET command values */
|
|
Interlock, /* SET IPL INTERLOCK */
|
|
Signal, /* SET IPL SIGNAL */
|
|
Wait /* SET IPL WAIT */
|
|
} SYNC_MODE;
|
|
|
|
static MTAB ipl_mod [] = {
|
|
/* Mask Value Match Value Print String Match String Validation Display Descriptor */
|
|
/* ---------- ----------- ----------------- ------------- -------------- ------- ---------- */
|
|
{ UNIT_DIAG, UNIT_DIAG, "diagnostic mode", "DIAGNOSTIC", &ipl_set_diag, NULL, NULL },
|
|
{ UNIT_DIAG, 0, "link mode", "LINK", &ipl_set_diag, NULL, NULL },
|
|
|
|
/* Entry Flags Value Print String Match String Validation Display Descriptor */
|
|
/* -------------------- ---------- ------------ ------------ -------------- --------------- ----------------- */
|
|
{ MTAB_XDV | MTAB_NMO, Interlock, "INTERLOCK", "INTERLOCK", &ipl_set_sync, &ipl_show_sync, NULL },
|
|
{ MTAB_XDV, Signal, NULL, "SIGNAL", &ipl_set_sync, NULL, NULL },
|
|
{ MTAB_XDV, Wait, NULL, "WAIT", &ipl_set_sync, NULL, NULL },
|
|
|
|
{ MTAB_XDV, 2u, "SC", "SC", &hp_set_dib, &hp_show_dib, (void *) &ipl_dib },
|
|
{ MTAB_XDV | MTAB_NMO, ~2u, "DEVNO", "DEVNO", &hp_set_dib, &hp_show_dib, (void *) &ipl_dib },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
/* Debugging trace lists */
|
|
|
|
static DEBTAB ipli_deb [] = {
|
|
{ "CMD", TRACE_CMD }, /* trace interface or controller commands */
|
|
{ "CSRW", TRACE_CSRW }, /* trace interface control, status, read, and write actions */
|
|
{ "PSERV", TRACE_PSERV }, /* trace periodic unit service scheduling calls and entries */
|
|
{ "XFER", TRACE_XFER }, /* trace data transmissions */
|
|
{ "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
static DEBTAB iplo_deb [] = {
|
|
{ "CMD", TRACE_CMD }, /* trace interface or controller commands */
|
|
{ "CSRW", TRACE_CSRW }, /* trace interface control, status, read, and write actions */
|
|
{ "STATE", TRACE_STATE }, /* trace state changes */
|
|
{ "PSERV", TRACE_PSERV }, /* trace periodic unit service scheduling calls and entries */
|
|
{ "XFER", TRACE_XFER }, /* trace data transmissions */
|
|
{ "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
|
|
/* Device descriptors */
|
|
|
|
DEVICE ipli_dev = {
|
|
"IPL", /* device name (logical name "IPLI") */
|
|
&poll_unit, /* unit array */
|
|
ipli_reg, /* register array */
|
|
ipl_mod, /* modifier array */
|
|
1, /* number of units */
|
|
10, /* address radix */
|
|
31, /* address width */
|
|
1, /* address increment */
|
|
16, /* data radix */
|
|
16, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&ipl_reset, /* reset routine */
|
|
&ipl_boot, /* boot routine */
|
|
&ipl_attach, /* attach routine */
|
|
&ipl_detach, /* detach routine */
|
|
&ipli_dib, /* device information block pointer */
|
|
DEV_DISABLE | DEV_DIS | DEV_DEBUG, /* device flags */
|
|
0, /* debug control flags */
|
|
ipli_deb, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
DEVICE iplo_dev = {
|
|
"IPLO", /* device name */
|
|
&sync_unit, /* unit array */
|
|
iplo_reg, /* register array */
|
|
ipl_mod, /* modifier array */
|
|
1, /* number of units */
|
|
10, /* address radix */
|
|
31, /* address width */
|
|
1, /* address increment */
|
|
16, /* data radix */
|
|
16, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
&ipl_reset, /* reset routine */
|
|
NULL, /* boot routine */
|
|
&ipl_attach, /* attach routine */
|
|
&ipl_detach, /* detach routine */
|
|
&iplo_dib, /* device information block pointer */
|
|
DEV_DISABLE | DEV_DIS | DEV_DEBUG, /* device flags */
|
|
0, /* debug control flags */
|
|
iplo_deb, /* debug flag name table */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
static DEVICE *dptrs [CARD_COUNT] = { /* device pointer array */
|
|
&ipli_dev,
|
|
&iplo_dev
|
|
};
|
|
|
|
|
|
|
|
/* IPL I/O interface routines */
|
|
|
|
|
|
|
|
/* Microcircuit interface.
|
|
|
|
In the link mode, the IPLI and IPLO devices are linked via a shared memory
|
|
region to the corresponding cards in another CPU instance. If only one or
|
|
the other device is in the diagnostic mode, we simulate the attachment of a
|
|
loopback connector to that device. If both devices are in the diagnostic
|
|
mode, we simulate the attachment of the interprocessor cable between IPLI and
|
|
IPLO in this machine.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. When tracing commands and status words, commands from this simulator
|
|
instance are sent on the outbound side of the output (higher select code)
|
|
card, and status is returned on the inbound side of the same card.
|
|
Commands from the other instance are received on the inbound side of the
|
|
input (lower select code) card, and status is returned on the outbound
|
|
side of the same card.
|
|
|
|
2. Command tracing is meaningless unless an HP 2000 Time-Shared BASIC
|
|
operating system is running. Testing for the shared memory allocation
|
|
that simulates the interconnecting cables indicates whether command
|
|
tracing is meaningful.
|
|
|
|
3. 2000 Access has a race condition that manifests itself by an apparently
|
|
normal boot and operational system console but no PLEASE LOG IN response
|
|
to terminals connected to the multiplexer. The frequency of occurrence
|
|
is higher on multiprocessor host systems, where the SP and IOP instances
|
|
may execute concurrently.
|
|
|
|
The cause is this code in the SP disc loader (source files S2883, S7900,
|
|
S79X0, S79X3, and S79XX):
|
|
|
|
LDA SDVTR REQUEST
|
|
JSB IOPMA,I DEVICE TABLE
|
|
[...]
|
|
STC DMAHS,C TURN ON DMA
|
|
SFS DMAHS WAIT FOR
|
|
JMP *-1 DEVICE TABLE
|
|
STC CH2,C SET CORRECT
|
|
CLC CH2 FLAG DIRECTION
|
|
|
|
DMA completion causes the SFS instruction to skip the JMP. The STC/CLC
|
|
pair at the end normally would cause a Processor Interconnect interrupt
|
|
and a second Request Device Table command to be recognized by the IOP,
|
|
except that the IOP DMA setup routine DMAXF (in source file SD61)
|
|
specifies an end-of-block CLC that holds off the interconnect interrupt,
|
|
and the DMA interrupt completion routine DMCMP ends with a STC,C that
|
|
clears the interconnect flag.
|
|
|
|
The SP program executes four instructions between DMA completion and the
|
|
CLC. The IOP program executes 34 instructions between the DMA completion
|
|
interrupt and the STC,C that resets the Processor Interconnect. In
|
|
hardware, the two CPUs are essentially interlocked by the DMA transfer,
|
|
and DMA completion occurs almost simultaneously in each machine.
|
|
Therefore, the STC/CLC in the SP is guaranteed to occur before the STC,C
|
|
in the IOP, and the Processor Interconnect interrupt never occurs. Under
|
|
simulation, and especially on multi-core hosts, that guarantee does not
|
|
hold. If host load preemption causes the STC/CLC to occur after the
|
|
STC,C, then the IOP starts a second device table DMA transfer, which the
|
|
SP is not expecting. Consequently, the IOP never processes the
|
|
subsequent Start Timesharing command, and the multiplexer does not
|
|
respond to user logon requests.
|
|
|
|
This situation can be avoided by using the SET IPL INTERLOCK command to
|
|
synchronize execution of the SP and IOP instances. The interlock value
|
|
is critical; it cannot be more than 16 instructions to allow for the
|
|
worst-case preemption scenario. That occurs when the SP instance does
|
|
not receive the last DMA input word during a poll that occurs at the last
|
|
instruction of the SP's interlock quantum, and then the IOP outputs the
|
|
last DMA word with the first instruction of its quantum. The IOP will
|
|
then execute a full quantum (16 instructions) and rendezvous with the SP.
|
|
If the SP blocks immediately after rendezvous, the IOP can then execute a
|
|
second full quantum (another 16 instructions) before the SP is able to
|
|
pick up the last word and execute its CLC. To avoid this, two interlock
|
|
times must be less than the critical instruction path length of 34
|
|
instructions.
|
|
|
|
Synchronization must remain active at least until the IOP has completed
|
|
its initialization. If synchronization events are not supported on the
|
|
host platform, the simulator employs a workaround that decreases the
|
|
incidence of the problem: the DMA output completion interrupt is delayed
|
|
to allow the other SIMH instance a chance to process its own DMA input
|
|
completion interrupt first. This improves the race condition by delaying
|
|
the IOP until the SP has a chance to receive the last word, recognize its
|
|
own DMA input completion, drop out of the SFS loop, and execute the
|
|
STC/CLC. The delay is initially set to one millisecond but is exposed
|
|
via a hidden IPLI register, EDTDELAY, that allows the user to lengthen
|
|
the delay if necessary.
|
|
|
|
Using this fallback mechanism instead of CPU synchronization only
|
|
improves the condition. It does not solve it because delaying the IOP
|
|
does not guarantee that the SP will actually execute. It is possible
|
|
that a higher-priority host process will preempt the SP, and that at the
|
|
delay expiration, the SP still has not executed the STC/CLC. Still, in
|
|
testing, the incidence dropped dramatically, so the problem is much less
|
|
intrusive.
|
|
*/
|
|
|
|
static SIGNALS_VALUE ipl_interface (const DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value)
|
|
{
|
|
const char * const iotype [] = { "Status", "Command" };
|
|
const CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* set the card selector */
|
|
static RESPONSE response [CARD_COUNT] = { None, None }; /* expected TSB command responses */
|
|
|
|
INBOUND_SIGNAL signal;
|
|
INBOUND_SET working_set = inbound_signals;
|
|
SIGNALS_VALUE outbound = { ioNONE, 0 };
|
|
t_bool irq_enabled = FALSE;
|
|
|
|
while (working_set) { /* while signals remain */
|
|
signal = IONEXTSIG (working_set); /* isolate the next signal */
|
|
|
|
switch (signal) { /* dispatch the I/O signal */
|
|
|
|
case ioCLF: /* Clear Flag flip-flop */
|
|
ipl [card].flag_buffer = CLEAR; /* reset the flag buffer */
|
|
ipl [card].flag = CLEAR; /* and flag flip-flops */
|
|
break;
|
|
|
|
|
|
case ioSTF: /* Set Flag flip-flop */
|
|
ipl [card].flag_buffer = SET; /* set the flag buffer flip-flop */
|
|
break;
|
|
|
|
|
|
case ioENF: /* Enable Flag */
|
|
if (ipl [card].flag_buffer == SET) /* if the flag buffer flip-flop is set */
|
|
ipl [card].flag = SET; /* then set the flag flip-flop */
|
|
break;
|
|
|
|
|
|
case ioSFC: /* Skip if Flag is Clear */
|
|
if (ipl [card].flag == CLEAR) /* if the flag flip-flop is clear */
|
|
outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */
|
|
break;
|
|
|
|
|
|
case ioSFS: /* Skip if Flag is Set */
|
|
if (ipl [card].flag == SET) /* if the flag flip-flop is set */
|
|
outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */
|
|
break;
|
|
|
|
|
|
case ioIOI: /* I/O data input */
|
|
outbound.value = ipl [card].input_word; /* get the return data */
|
|
|
|
tpprintf (dptrs [card], TRACE_CSRW, "%s input word is %06o\n",
|
|
iotype [card ^ 1], ipl [card].input_word);
|
|
|
|
if (TRACINGP (dptrs [card], TRACE_CMD) && shared_ptr != NULL)
|
|
if (card == iplo)
|
|
response [card] = trace_status (card, outbound.value, response [card]);
|
|
else
|
|
response [card] = trace_command (card, outbound.value, response [card]);
|
|
break;
|
|
|
|
|
|
case ioIOO: /* I/O data output */
|
|
ipl [card].output_word = inbound_value; /* clear supplied status */
|
|
|
|
io_ptrs [card].output->data_out = ipl [card].output_word; /* place the word on the data bus */
|
|
|
|
tpprintf (dptrs [card], TRACE_CSRW, "%s output word is %06o\n",
|
|
iotype [card], ipl [card].output_word);
|
|
|
|
if (TRACINGP (dptrs [card], TRACE_CMD) && shared_ptr != NULL)
|
|
if (card == iplo)
|
|
response [card] = trace_command (card, inbound_value, response [card]);
|
|
else
|
|
response [card] = trace_status (card, inbound_value, response [card]);
|
|
break;
|
|
|
|
|
|
case ioPOPIO: /* Power-On Preset to I/O */
|
|
ipl [card].flag_buffer = SET; /* set the flag buffer flip-flop */
|
|
ipl [card].output_word = 0; /* and clear the output register */
|
|
|
|
io_ptrs [card].output->data_out = 0;
|
|
break;
|
|
|
|
|
|
case ioCRS: /* Control Reset */
|
|
ipl [card].control = CLEAR; /* clear the control flip-flop */
|
|
break;
|
|
|
|
|
|
case ioCLC: /* Clear Control flip-flop */
|
|
ipl [card].control = CLEAR; /* clear the control flip-flop */
|
|
break;
|
|
|
|
|
|
case ioSTC: /* Set Control flip-flop */
|
|
ipl [card].control = SET; /* set the control flip-flop */
|
|
|
|
io_ptrs [card].output->device_command_out = TRUE; /* assert Device Command */
|
|
|
|
tpprintf (dptrs [card], TRACE_XFER, "Word %06o sent to link\n",
|
|
ipl [card].output_word);
|
|
|
|
sim_cancel (&poll_unit); /* reschedule the poll immediately */
|
|
activate_unit (&poll_unit, 1); /* as we're expecting a response */
|
|
break;
|
|
|
|
|
|
case ioEDT: /* end data transfer */
|
|
response [card] = None; /* clear data response */
|
|
|
|
if (cpu_is_iop /* if this is the IOP instance */
|
|
&& inbound_signals & ioIOO /* and the card is doing output */
|
|
&& card == ipli /* on the input card */
|
|
&& edt_delay > 0) { /* and a delay is specified */
|
|
sim_os_ms_sleep (edt_delay); /* then delay DMA completion */
|
|
|
|
tprintf (ipli_dev, TRACE_CMD, "Delayed DMA completion interrupt for %d msec\n",
|
|
edt_delay);
|
|
}
|
|
break;
|
|
|
|
|
|
case ioSIR: /* Set Interrupt Request */
|
|
if (ipl [card].control & ipl [card].flag) /* if the control and flag flip-flops are set */
|
|
outbound.signals |= cnVALID; /* then deny PRL */
|
|
else /* otherwise */
|
|
outbound.signals |= cnPRL | cnVALID; /* conditionally assert PRL */
|
|
|
|
if (ipl [card].control & ipl [card].flag /* if the control and flag */
|
|
& ipl [card].flag_buffer) /* and flag buffer flip-flops are set */
|
|
outbound.signals |= cnIRQ | cnVALID; /* then conditionally assert IRQ */
|
|
|
|
if (ipl [card].flag == SET) /* if the flag flip-flop is set */
|
|
outbound.signals |= ioSRQ; /* then assert SRQ */
|
|
break;
|
|
|
|
|
|
case ioIAK: /* Interrupt Acknowledge */
|
|
ipl [card].flag_buffer = CLEAR; /* clear the flag buffer flip-flop */
|
|
break;
|
|
|
|
|
|
case ioIEN: /* Interrupt Enable */
|
|
irq_enabled = TRUE; /* permit IRQ to be asserted */
|
|
break;
|
|
|
|
|
|
case ioPRH: /* Priority High */
|
|
if (irq_enabled && outbound.signals & cnIRQ) /* if IRQ is enabled and conditionally asserted */
|
|
outbound.signals |= ioIRQ | ioFLG; /* then assert IRQ and FLG */
|
|
|
|
if (!irq_enabled || outbound.signals & cnPRL) /* if IRQ is disabled or PRL is conditionally asserted */
|
|
outbound.signals |= ioPRL; /* then assert it unconditionally */
|
|
break;
|
|
|
|
|
|
case ioPON: /* not used by this interface */
|
|
break;
|
|
}
|
|
|
|
IOCLEARSIG (working_set, signal); /* remove the current signal from the set */
|
|
} /* and continue until all signals are processed */
|
|
|
|
return outbound; /* return the outbound signals and value */
|
|
}
|
|
|
|
|
|
|
|
/* IPL interface local SCP support routines */
|
|
|
|
|
|
|
|
/* Set the diagnostic or link mode.
|
|
|
|
This validation routine is entered with the "value" parameter set to zero if
|
|
the unit is to be set into the link (normal) mode or non-zero if the unit is
|
|
to be set into the diagnostic mode. The character and descriptor pointers
|
|
are not used.
|
|
|
|
In addition to setting or clearing the UNIT_DIAG flag, the I/O state pointers
|
|
are set to point at the appropriate state structure. The selected pointer
|
|
configuration depends on whether none, one, or both the IPLI and IPLO devices
|
|
are in diagnostic mode.
|
|
|
|
If both devices are in diagnostic mode, the pointers are set to point at
|
|
their respective state structures but with the input and output pointers
|
|
reversed. This simulates connecting one of the interprocessor cables between
|
|
the two cards within the same CPU, permitting the Processor Interconnect
|
|
Cable Diagnostic to be run.
|
|
|
|
If only one of the devices is in diagnostic mode, the pointers are set to
|
|
point at the device's state structure with the input and output pointers
|
|
reversed. This simulates connected a loopback connector to the card,
|
|
permitting the General Register Diagnostic to be run.
|
|
|
|
If a device is in link mode, that device's pointers are set to point at the
|
|
corresponding parts of the device's state structure. This simulates a card
|
|
with no cable connected.
|
|
|
|
If the device is attached, setting it into diagnostic mode will detach it
|
|
first.
|
|
*/
|
|
|
|
static t_stat ipl_set_diag (UNIT *uptr, int32 value, char *cptr, void *desc)
|
|
{
|
|
const IO_STATE_PTR isp = local_region.dev_bus; /* pointer to the local bus state */
|
|
|
|
if (value) { /* if this is an entry into diagnostic mode */
|
|
ipl_detach (uptr); /* then detach it first */
|
|
uptr->flags |= UNIT_DIAG; /* before setting the flag */
|
|
}
|
|
|
|
else /* otherwise this is an entry into link mode */
|
|
uptr->flags &= ~UNIT_DIAG; /* so clear the flag */
|
|
|
|
if (poll_unit.flags & sync_unit.flags & UNIT_DIAG) { /* if both devices are now in diagnostic mode */
|
|
io_ptrs [ipli].input = &isp [iplo].reverse.input; /* then connect the cable */
|
|
io_ptrs [ipli].output = &isp [ipli].forward.output; /* so that the outputs of one card */
|
|
io_ptrs [iplo].input = &isp [ipli].reverse.input; /* are connected to the inputs of the other card */
|
|
io_ptrs [iplo].output = &isp [iplo].forward.output; /* and vice versa */
|
|
|
|
io_ptrs [ipli].output->cable_connected = TRUE; /* indicate that the cable */
|
|
io_ptrs [iplo].output->cable_connected = TRUE; /* has been connected between the cards */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
if (poll_unit.flags & UNIT_DIAG) { /* if the input card is in diagnostic mode */
|
|
io_ptrs [ipli].input = &isp [ipli].reverse.input; /* then loop the card outputs */
|
|
io_ptrs [ipli].output = &isp [ipli].forward.output; /* back to the inputs and vice versa */
|
|
io_ptrs [ipli].output->cable_connected = TRUE; /* and indicate that the card is connected */
|
|
}
|
|
|
|
else { /* otherwise the card is in link mode */
|
|
io_ptrs [ipli].input = &isp [ipli].forward.input; /* so point at the card state */
|
|
io_ptrs [ipli].output = &isp [ipli].forward.output; /* in the normal direction */
|
|
io_ptrs [ipli].output->cable_connected = FALSE; /* and indicate that the card is not connected */
|
|
}
|
|
|
|
if (sync_unit.flags & UNIT_DIAG) { /* otherwise */
|
|
io_ptrs [iplo].input = &isp [iplo].reverse.input; /* if the output card is in diagnostic mode */
|
|
io_ptrs [iplo].output = &isp [iplo].forward.output; /* then loop the card outputs */
|
|
io_ptrs [iplo].output->cable_connected = TRUE; /* back to the inputs and vice versa */
|
|
} /* and indicate that the card is connected */
|
|
|
|
else {
|
|
io_ptrs [iplo].input = &isp [iplo].forward.input; /* otherwise the card is in link mode */
|
|
io_ptrs [iplo].output = &isp [iplo].forward.output; /* so point at the card state */
|
|
io_ptrs [iplo].output->cable_connected = FALSE; /* in the normal direction */
|
|
} /* and indicate that the card is not connected */
|
|
}
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Synchronize the simulator instance.
|
|
|
|
This validation routine is entered with the "uptr" parameter pointing
|
|
at the input unit and the "value" parameter set to the selected
|
|
synchronization command. The character and descriptor pointers are not used.
|
|
|
|
This routine is called for the following commands:
|
|
|
|
SET IPL INTERLOCK=<n>
|
|
SET IPL SIGNAL
|
|
SET IPL WAIT
|
|
|
|
Setting a non-zero interlock value establishes instruction synchronization
|
|
between the simulator instances and starts the synchronizer. The count gives
|
|
the number of instructions that are executed before a rendezvous is
|
|
attempted. If the count is set to 0, the synchronizer is stopped, and the
|
|
instances execute freely.
|
|
|
|
The WAIT command causes the simulator to wait until the event signal is
|
|
received from the other instance. The SIGNAL command sends the event signal
|
|
to the other instance.
|
|
|
|
For all three commands, if the shared memory area used to communicate between
|
|
the instances has not been created yet by attaching the IPL device, the
|
|
routine returns "Unit not attached" status. If the unit is attached but
|
|
event creation failed, "Command not allowed" status is returned. If the
|
|
signal or wait function returned an execution error, the routine returns
|
|
"Command not completed" to indicate that the error code register should be
|
|
checked.
|
|
|
|
To permit the user to abort the WAIT command, waits of 100 milliseconds each
|
|
are performed in a loop. At each pass through the loop, the keyboard is
|
|
polled for a CTRL+E, and the wait is abandoned if one if seen. If the user
|
|
aborts the wait, "Command not completed" status is returned.
|
|
|
|
If the unit is attached but the synchronization events are not defined, the
|
|
WAIT command falls back to a timed wait, and the SIGNAL command falls back to
|
|
doing nothing. This allows at least a chance that these commands will
|
|
suffice on systems that does not support synchronization. If the INTERLOCK
|
|
command is called, it will return a "Command not allowed" error, as there is
|
|
no practicable fallback mechanism for instruction synchronization.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "wait_event" routine returns TRUE if the event is signaled and FALSE
|
|
if it times out while waiting.
|
|
|
|
2. UNIX systems do not pass CTRL+E through the keyboard interface but
|
|
instead signal SIGINT. To catch these, a SIGINT handler is installed
|
|
that sets the "wait_aborted" variable TRUE if it is called. The variable
|
|
is checked in the loop, and the wait is abandoned if it is set.
|
|
|
|
3. The console must be changed to non-blocking mode in order to obtain the
|
|
CTRL+E keystroke without requiring a newline terminator. Bracketing
|
|
calls to "sim_ttrun" and "sim_ttcmd" are used to do this, even though
|
|
they also drop the simulator priority, which is unnecessary.
|
|
*/
|
|
|
|
static t_stat ipl_set_sync (UNIT *uptr, int32 value, char *cptr, void *desc)
|
|
{
|
|
typedef void (*SIG_HANDLER) (int); /* signal handler function type */
|
|
const uint32 wait_time = 100; /* the wait time in milliseconds */
|
|
const uint32 count_base = 10; /* the radix for the interlock count */
|
|
const t_value count_max = UINT_MAX; /* the maximum interlock count value */
|
|
SIG_HANDLER prior_handler;
|
|
t_bool signaled;
|
|
t_value count;
|
|
t_stat status = SCPE_OK;
|
|
|
|
if (shared_ptr == NULL) /* if shared memory has not been allocated */
|
|
status = SCPE_UNATT; /* then report that the unit must be attached first */
|
|
|
|
else switch ((SYNC_MODE) value) { /* otherwise dispatch on the selected command */
|
|
|
|
case Interlock: /* SET IPL INTERLOCK */
|
|
if (lock_id == UNDEFINED_EVENT /* if the lock event */
|
|
|| unlock_id == UNDEFINED_EVENT) /* or unlock event has not been defined yet */
|
|
status = SCPE_NOFNC; /* then the command is not allowed */
|
|
|
|
else if (cptr == NULL || *cptr == '\0') /* otherwise if the expected value is missing */
|
|
status = SCPE_MISVAL; /* then report the error */
|
|
|
|
else { /* otherwise an interlock count is present */
|
|
count = get_uint (cptr, count_base, /* so parse the supplied value */
|
|
count_max, &status);
|
|
|
|
if (status == SCPE_OK) { /* if it is valid */
|
|
shared_ptr->count = (uint32) count; /* then set the new control value */
|
|
|
|
if (count == 0) { /* if asynchronous mode is specified */
|
|
release_wait (lock_id, Locked, Unlocked); /* then release the lock and unlock events */
|
|
release_wait (unlock_id, Unlocking, Unlocked); /* in case the other instance is waiting */
|
|
|
|
shared_ptr->gate = Unlocked; /* reset to the initial state */
|
|
sim_cancel (&sync_unit); /* and stop the synchronizer */
|
|
|
|
sync_unit.wait = 0; /* indicate asynchronous mode */
|
|
|
|
tprintf (iplo_dev, TRACE_PSERV, "Synchronizer stopped\n");
|
|
}
|
|
|
|
else { /* otherwise synchronous mode is specified */
|
|
if (sync_unit.wait == 0) /* report if changing modes */
|
|
tprintf (iplo_dev, TRACE_PSERV, "Synchronizer started\n");
|
|
|
|
sync_unit.wait = (int32) count; /* record the interlock time */
|
|
sim_activate_abs (&sync_unit, sync_unit.wait); /* and start the synchronizer */
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
|
|
case Signal: /* SET IPL SIGNAL */
|
|
if (sync_id == UNDEFINED_EVENT) /* if the event has not been defined yet */
|
|
if (uptr->flags & UNIT_ATT) { /* but the unit is currently attached */
|
|
status = SCPE_OK; /* then fall back to an emulated SIGNAL */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Event signal emulated\n");
|
|
}
|
|
|
|
else /* otherwise */
|
|
status = SCPE_NOFNC; /* the command is not allowed */
|
|
|
|
else { /* otherwise */
|
|
event_error = signal_event (sync_id); /* signal the event */
|
|
|
|
if (event_error == 0) /* if signaling succeeded */
|
|
status = SCPE_OK; /* then report command success */
|
|
else /* otherwise */
|
|
status = SCPE_INCOMP; /* report that the command did not complete */
|
|
}
|
|
break;
|
|
|
|
|
|
case Wait:
|
|
if (sync_id == UNDEFINED_EVENT) /* if the event has not been defined yet */
|
|
if (uptr->flags & UNIT_ATT) { /* but the unit is currently attached */
|
|
sim_os_sleep (fallback_wait); /* then fall back to an emulated WAIT */
|
|
status = SCPE_OK; /* and then return success */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Event wait emulated\n");
|
|
}
|
|
|
|
else /* otherwise */
|
|
status = SCPE_NOFNC; /* the command is not allowed */
|
|
|
|
else { /* otherwise */
|
|
wait_aborted = FALSE; /* clear the abort flag */
|
|
|
|
prior_handler = signal (SIGINT, wru_handler); /* install our WRU handler in place of the current one */
|
|
|
|
if (prior_handler == SIG_ERR) /* if installation failed */
|
|
status = SCPE_SIGERR; /* then report an error */
|
|
|
|
else { /* otherwise */
|
|
status = sim_ttrun (); /* switch the console to non-blocking mode */
|
|
|
|
if (status != SCPE_OK) /* if the switch failed */
|
|
return status; /* then report the error and quit */
|
|
|
|
do { /* otherwise */
|
|
event_error = wait_event (sync_id, wait_time, /* wait for the event */
|
|
&signaled); /* to be signaled */
|
|
|
|
if (signaled == FALSE) { /* if the wait timed out instead */
|
|
status = sim_os_poll_kbd (); /* then check for a CTRL+E keypress */
|
|
|
|
if (status >= SCPE_KFLAG) /* if a regular key was pressed */
|
|
status = SCPE_OK; /* then ignore it */
|
|
}
|
|
}
|
|
while (! (signaled /* continue to wait until the event is signaled */
|
|
|| wait_aborted /* or the wait is aborted by the user */
|
|
|| status != SCPE_OK /* or an SCP error occurs */
|
|
|| event_error != 0)); /* or an event error occurs */
|
|
|
|
if (wait_aborted || status == SCPE_STOP /* if the wait was aborted by the user */
|
|
|| status == SCPE_OK && event_error != 0) /* or an event error occurred */
|
|
status = SCPE_INCOMP; /* then report that the command did not complete */
|
|
|
|
sim_ttcmd (); /* restore the console to blocking mode */
|
|
}
|
|
|
|
if (status != SCPE_SIGERR) /* if the signal handler was set up properly */
|
|
signal (SIGINT, prior_handler); /* then restore the prior handler */
|
|
}
|
|
break;
|
|
|
|
default: /* any other command value */
|
|
status = SCPE_IERR; /* results in an internal error */
|
|
break;
|
|
}
|
|
|
|
return status; /* return the operation status */
|
|
}
|
|
|
|
|
|
/* Show the interlock count.
|
|
|
|
This display routine is called to show the number of instructions executed
|
|
before a rendezvous occurs. The output stream is passed in the "st"
|
|
parameter, and the other parameters are ignored. If the count is zero, then
|
|
the instance is executing asynchronously. Otherwise, the count is printed.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The return status from display routines is ignored, so we must print any
|
|
error message text here before returning. As a consequence, a command
|
|
file with an invalid SHOW command will continue to execute.
|
|
*/
|
|
|
|
static t_stat ipl_show_sync (FILE *st, UNIT *uptr, int32 value, void *desc)
|
|
{
|
|
if (shared_ptr == NULL) /* if shared memory has not been allocated */
|
|
fprintf (st, "%s\n", sim_error_text (SCPE_UNATT)); /* then report that the unit must be attached first */
|
|
|
|
else if (sync_unit.wait == 0) /* otherwise if the interlock unit is not active */
|
|
fputs ("Asynchronous execution\n", st); /* then we are operating asynchronously */
|
|
|
|
else /* otherwise report the count */
|
|
fprintf (st, "Synchronous execution, interlock = %u\n", shared_ptr->count);
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
|
|
/* IPL device local SCP support routines */
|
|
|
|
|
|
|
|
/* Reset the IPL.
|
|
|
|
This routine is called for a RESET, RESET IPLI, or RESET IPLO command. It is
|
|
the simulation equivalent of the POPIO signal, which is asserted by the front
|
|
panel PRESET switch.
|
|
|
|
For a power-on reset, the logical name "IPLI" is assigned to the first
|
|
processor interconnect card, so that it may referenced either as that name or
|
|
as "IPL" for use when a SET command affects both interfaces. The interlock
|
|
statistics are also reset.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The IPLI logical name cannot point to a static string. Instead, it must
|
|
be dynamically allocated because the user might DEASSIGN it, and that
|
|
command will attempt to free the memory area.
|
|
*/
|
|
|
|
static t_stat ipl_reset (DEVICE *dptr)
|
|
{
|
|
UNIT *uptr = dptr->units;
|
|
DIB *dibptr = (DIB *) dptr->ctxt; /* DIB pointer */
|
|
CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* card number */
|
|
|
|
hp_enbdis_pair (dptr, dptrs [card ^ 1]); /* ensure that the pair state is consistent */
|
|
|
|
if (sim_switches & SWMASK ('P')) { /* initialization reset? */
|
|
ipl [card].input_word = 0; /* clear the */
|
|
ipl [card].output_word = 0; /* data registers */
|
|
|
|
if (card == ipli) { /* if this is the input card */
|
|
sync_avg = 0; /* then clear */
|
|
sync_max = 0; /* the interlock */
|
|
sync_cnt = 0; /* statistics */
|
|
sync_mean = 0.0;
|
|
|
|
if (dptr->lname == NULL) /* if the logical name has not been assigned yet */
|
|
dptr->lname = strdup ("IPLI"); /* then allocate and initialize it */
|
|
}
|
|
}
|
|
|
|
io_assert (dptr, ioa_POPIO); /* PRESET the device */
|
|
|
|
if (uptr->flags & UNIT_ATT) /* if the link is active for this card */
|
|
if (card == ipli) /* then if this is the input card */
|
|
activate_unit (uptr, poll_wait); /* then continue to poll for input at the idle rate */
|
|
|
|
else { /* otherwise */
|
|
sync_unit.wait = shared_ptr->count; /* reestablish the interlock count */
|
|
|
|
if (shared_ptr->count == 0) /* if the mode is asynchronous */
|
|
sim_cancel (uptr); /* then cancel the synchronizer */
|
|
else /* otherwise */
|
|
sim_activate_abs (uptr, shared_ptr->count); /* schedule the synchronizer */
|
|
}
|
|
|
|
else /* otherwise the link is inactive */
|
|
sim_cancel (&poll_unit); /* so stop input polling */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* Attach one end of the interconnecting cables.
|
|
|
|
This routine connects the IPL device pair to a shared memory region. This
|
|
simulates connecting one end of the processor interconnect kit cables to the
|
|
card pair in this CPU. The command is:
|
|
|
|
ATTACH [ -S | -I ] [ -E ] IPL <code>
|
|
|
|
...where <code> is a user-selected decimal number between 1 and 65535 that
|
|
uniquely identifies the instance pair to interconnect. The -S or -I switch
|
|
indicates whether this instance is acting as the System Processor or the I/O
|
|
Processor. The -E switch indicates that the command should succeed even if
|
|
the synchronization events cannot be created (e.g., are unsupported on the
|
|
host system). The command will be rejected if either device is in diagnostic
|
|
mode, or if the <code> is omitted, malformed, or out of range.
|
|
|
|
For backward compatibility with prior IPL implementations that used network
|
|
interconnections, the following commands are also accepted:
|
|
|
|
ATTACH [ -L ] [ -E ] [ IPLI | IPLO] <port-1>
|
|
ATTACH -C [ -E ] [ IPLI | IPLO] <port-2>
|
|
|
|
For these commands, -L or no switch indicates the SP instance, and -C
|
|
indicates the IOP instance. <port-1> and <port-2> used to indicate the
|
|
network port numbers to use, but now it serves only to supply the code
|
|
number from the lesser of the two values.
|
|
|
|
Local memory is allocated to hold the code number string, which serves as the
|
|
"attached file name" for the SCP SHOW command. If memory allocation fails,
|
|
the command is rejected.
|
|
|
|
This routine creates a shared memory region and three numbered events (or
|
|
semaphores) that are used to coordinate a data exchange with the other
|
|
simulator instance. If -S or -I is specified, then creation occurs after the
|
|
ATTACH command is given for either the IPLI or IPLO device, and both devices
|
|
are marked as attached. If -L or -C is specified, then both devices must be
|
|
attached before creation occurs using the lower port number.
|
|
|
|
Object names that identify the shared memory region and synchronization
|
|
events are derived from the <code> (or lower <port>) number and <event>
|
|
number:
|
|
|
|
/HP 2100-MEM-<code>
|
|
/HP 2100-EVT-<code>-<event>
|
|
|
|
Event number 1 is used for the SIGNAL and WAIT commands. Event numbers 2 and
|
|
3 are used by the instruction interlock service routine.
|
|
|
|
Each simulator instance must use the same <code> (or <port> pair) when
|
|
attaching for the interconnection to occur. This permits multiple instance
|
|
pairs to operate simultaneously and independently, if desired.
|
|
|
|
Once shared memory is allocated, pointers to the region for the SP and IOP
|
|
instances are set so that the output card pointer of one instance and the
|
|
input card pointer of the other instance reference the same memory structure.
|
|
This accomplishes the interconnection, as a write to one instance's card will
|
|
be seen by a read from the other instance's card.
|
|
|
|
If the shared memory allocation succeeds but the process synchronization
|
|
event creations fail, "Command not completed" is printed on the console to
|
|
indicate that interconnection without synchronization is permitted. If the
|
|
-E switch is specified, command file execution will continue; otherwise, the
|
|
error will cause command files to abort.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The implementation supports process synchronization only on the local
|
|
system.
|
|
|
|
2. The object names begin with slashes to conform to POSIX requirements to
|
|
guarantee that multiple instances to refer to the same shared memory
|
|
region. Omitting the slash results in implementation-defined behavior on
|
|
POSIX systems.
|
|
|
|
3. The shared memory region is automatically initialized to zero when it is
|
|
originally allocated.
|
|
|
|
4. Zeroing the "tsb_version" field of the shared memory area on creation is
|
|
equivalent to setting it to the "HP_2000BC" enumeration value.
|
|
*/
|
|
|
|
static t_stat ipl_attach (UNIT *uptr, char *cptr)
|
|
{
|
|
t_stat status;
|
|
int32 id_number;
|
|
size_t last_index;
|
|
char object_name [PATH_MAX];
|
|
char *tptr, *zptr;
|
|
IO_STATE_PTR isp;
|
|
UNIT *optr;
|
|
|
|
if ((poll_unit.flags | sync_unit.flags) & UNIT_DIAG) /* if either unit is in diagnostic mode */
|
|
return SCPE_NOFNC; /* then the command is not allowed */
|
|
|
|
else if (uptr->flags & UNIT_ATT) /* otherwise if the unit is currently attached */
|
|
ipl_detach (uptr); /* then detach it first */
|
|
|
|
id_number = (int32) strtotv (cptr, &zptr, 10); /* parse the command for the ID number */
|
|
|
|
if (cptr == zptr || *zptr != '\0' || id_number == 0) /* if the parse failed or extra characters or out of range */
|
|
return SCPE_ARG; /* then reject the attach with an invalid argument error */
|
|
|
|
else { /* otherwise a single number was specified */
|
|
tptr = (char *) malloc (strlen (cptr) + 1); /* so allocate a string buffer to hold the ID */
|
|
|
|
if (tptr == NULL) /* if the allocation failed */
|
|
return SCPE_MEM; /* then reject the attach with an out-of-memory error */
|
|
|
|
else { /* otherwise */
|
|
strcpy (tptr, cptr); /* copy the ID number to the buffer */
|
|
uptr->filename = tptr; /* and assign it as the attached object name */
|
|
|
|
uptr->flags |= UNIT_ATT; /* set the unit attached flag */
|
|
uptr->ID = id_number; /* and save the ID number */
|
|
|
|
activate_unit (&poll_unit, poll_wait); /* activate the poll unit using the initial wait */
|
|
}
|
|
|
|
if ((sim_switches & (SP | IOP)) == 0) /* if this is not a single-device attach */
|
|
if (poll_unit.ID == 0 || sync_unit.ID == 0) /* then if both devices have not been attached yet */
|
|
return SCPE_OK; /* then we've done all we can do */
|
|
|
|
else if (poll_unit.ID < sync_unit.ID) /* otherwise */
|
|
id_number = poll_unit.ID; /* determine */
|
|
else /* the lower */
|
|
id_number = sync_unit.ID; /* ID number */
|
|
|
|
else { /* otherwise this is a single-device attach */
|
|
if (uptr == &poll_unit) /* so if we are attaching the input unit */
|
|
optr = &sync_unit; /* then point at the output unit */
|
|
else /* otherwise we are attaching the output unit */
|
|
optr = &poll_unit; /* so point at the input unit */
|
|
|
|
optr->filename = tptr; /* assign the ID as the attached object name */
|
|
|
|
optr->flags |= UNIT_ATT; /* set the unit attached flag */
|
|
optr->ID = id_number; /* and save the ID number */
|
|
}
|
|
|
|
sprintf (object_name, "/%s-MEM-%d", /* generate the shared memory area name */
|
|
sim_name, id_number);
|
|
|
|
status = sim_shmem_open (object_name, sizeof (SHARED_REGION), /* allocate the shared memory area */
|
|
&shared_id, (void **) &shared_ptr);
|
|
|
|
if (status != SCPE_OK) { /* if the allocation failed */
|
|
ipl_detach (uptr); /* then detach this unit */
|
|
return status; /* and report the error */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
isp = shared_ptr->dev_bus; /* point at the shared I/O device state */
|
|
|
|
cpu_is_iop = ((sim_switches & (CONNECT | IOP)) != 0); /* -C or -I imply that this is the I/O Processor */
|
|
|
|
if (cpu_is_iop) { /* if this is the IOP instance */
|
|
io_ptrs [ipli].input = &isp [iplo].reverse.input; /* then cross-connect */
|
|
io_ptrs [ipli].output = &isp [iplo].reverse.output; /* the input and output */
|
|
io_ptrs [iplo].input = &isp [ipli].reverse.input; /* interface cards to the */
|
|
io_ptrs [iplo].output = &isp [ipli].reverse.output; /* SP interface cards */
|
|
|
|
if (cpu_configuration & CPU_IOP) /* if IOP firmware is installed */
|
|
shared_ptr->tsb_version = HP_2000_Access; /* then Access is being run */
|
|
}
|
|
|
|
else { /* otherwise this is the SP instance */
|
|
io_ptrs [ipli].input = &isp [ipli].forward.input; /* so connect */
|
|
io_ptrs [ipli].output = &isp [ipli].forward.output; /* the interface cards */
|
|
io_ptrs [iplo].input = &isp [iplo].forward.input; /* to the I/O cables */
|
|
io_ptrs [iplo].output = &isp [iplo].forward.output; /* directly */
|
|
|
|
if ((cpu_configuration & CPU_FP) /* if floating-point firmware is installed in the SP */
|
|
&& shared_ptr->tsb_version != HP_2000_Access) /* and Access is not being run */
|
|
shared_ptr->tsb_version = HP_2000F; /* then 2000F is being run */
|
|
}
|
|
|
|
io_ptrs [ipli].output->cable_connected = TRUE; /* indicate that the cables to the other set */
|
|
io_ptrs [iplo].output->cable_connected = TRUE; /* have been connected */
|
|
|
|
sync_unit.wait = shared_ptr->count; /* save the unit activation time */
|
|
|
|
if (shared_ptr->count > 0) /* if the count has been set by the other */
|
|
sim_activate_abs (&sync_unit, shared_ptr->count); /* simulator then start the synchronizer */
|
|
}
|
|
|
|
sprintf (event_name, "/%s-EVT-%d-1", /* generate the process synchronization event name */
|
|
sim_name, id_number);
|
|
|
|
last_index = strlen (event_name) - 1; /* get the index of the event digit */
|
|
|
|
event_error = create_event (event_name, &sync_id); /* create the first event */
|
|
|
|
if (event_error == 0) { /* if creation succeeded */
|
|
event_name [last_index]++; /* then increment the event digit */
|
|
event_error = create_event (event_name, &lock_id); /* and create the second event */
|
|
}
|
|
|
|
if (event_error == 0) { /* if creation succeeded */
|
|
event_name [last_index]++; /* then increment the event digit */
|
|
event_error = create_event (event_name, &unlock_id); /* and create the third event */
|
|
}
|
|
|
|
if (event_error == 0) /* if event creation succeeded */
|
|
return SCPE_OK; /* then report a successful attach */
|
|
|
|
else if (sim_switches & SWMASK ('E')) { /* otherwise if fallback is enabled */
|
|
cputs (sim_error_text (SCPE_INCOMP)); /* then report that */
|
|
cputc ('\n'); /* the command did not complete */
|
|
return SCPE_OK; /* but return success */
|
|
}
|
|
|
|
else /* otherwise */
|
|
return SCPE_INCOMP; /* report that the command did not complete */
|
|
}
|
|
}
|
|
|
|
|
|
/* Detach the interconnecting cables.
|
|
|
|
This routine disconnects the IPL device pair from the shared memory region.
|
|
This simulates disconnecting the processor interconnect kit cables from the
|
|
card pair in this CPU. The command is:
|
|
|
|
DETACH IPL
|
|
|
|
For backward compatibility with prior IPL implementations that used network
|
|
interconnections, the following commands are also accepted:
|
|
|
|
DETACH IPLI
|
|
DETACH IPLO
|
|
|
|
In either case, the shared memory region and process synchronization events
|
|
are destroyed, and the card state pointers are reset to point at the local
|
|
memory structure. If a single ATTACH was done, a single DETACH will detach
|
|
both devices and free the allocated "file name" memory. The input poll and
|
|
synchronizer are also stopped.
|
|
|
|
If the event destruction failed, the routine returns "Command not completed"
|
|
status to indicate that the error code register should be checked.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Deallocation of the shared memory region and destruction of the
|
|
synchronization events occur only when the second of the two simulator
|
|
instances detaches, i.e., when there are no more attached processes
|
|
remaining.
|
|
|
|
2. The attached "file name" of both units point to the same memory
|
|
allocation, so only one deallocation is required.
|
|
*/
|
|
|
|
static t_stat ipl_detach (UNIT *uptr)
|
|
{
|
|
IO_STATE_PTR isp = local_region.dev_bus; /* a pointer to the local bus state */
|
|
size_t last_index;
|
|
UNIT *optr;
|
|
|
|
if ((uptr->flags & UNIT_ATT) == 0) /* if the unit is not attached */
|
|
if (sim_switches & SIM_SW_REST) /* then if this is a restoration call */
|
|
return SCPE_OK; /* then return success */
|
|
else /* otherwise this is a manual request */
|
|
return SCPE_UNATT; /* so complain that the unit is not attached */
|
|
|
|
if (poll_unit.filename == sync_unit.filename) { /* if both units are attached to the same object */
|
|
if (uptr == &poll_unit) /* then if we are detaching the input unit */
|
|
optr = &sync_unit; /* then point at the output unit */
|
|
else /* otherwise we are detaching the output unit */
|
|
optr = &poll_unit; /* so point at the input unit */
|
|
|
|
optr->filename = NULL; /* clear the other unit's attached object name */
|
|
|
|
optr->flags &= ~UNIT_ATT; /* clear the other unit's attached flag */
|
|
optr->ID = 0; /* and the ID number */
|
|
}
|
|
|
|
free (uptr->filename); /* free the memory holding the ID number */
|
|
uptr->filename = NULL; /* and clear the attached object name */
|
|
|
|
uptr->flags &= ~UNIT_ATT; /* clear the unit attached flag */
|
|
uptr->ID = 0; /* and the ID number */
|
|
|
|
sim_cancel (&poll_unit); /* cancel the poll */
|
|
sim_cancel (&sync_unit); /* and the synchronizer */
|
|
|
|
sync_unit.wait = 0; /* enter asynchronous mode */
|
|
|
|
io_ptrs [ipli].output->cable_connected = FALSE; /* disconnect the cables */
|
|
io_ptrs [iplo].output->cable_connected = FALSE; /* from both cards */
|
|
|
|
io_ptrs [ipli].input = &isp [ipli].forward.input; /* restore local control */
|
|
io_ptrs [ipli].output = &isp [ipli].forward.output; /* over the I/O state */
|
|
io_ptrs [iplo].input = &isp [iplo].forward.input; /* for both cards */
|
|
io_ptrs [iplo].output = &isp [iplo].forward.output;
|
|
|
|
if (shared_ptr != NULL) { /* if shared memory has been allocated */
|
|
ipl_set_sync (uptr, Interlock, "0", NULL); /* then disable interlocking and release any waits */
|
|
|
|
sim_shmem_close (shared_id); /* deallocate the shared memory region */
|
|
shared_ptr = NULL; /* and clear the region pointer */
|
|
}
|
|
|
|
last_index = strlen (event_name) - 1; /* get the index of the event digit */
|
|
|
|
event_name [last_index] = '1';
|
|
event_error = destroy_event (event_name, &sync_id); /* destroy the first event */
|
|
|
|
if (event_error == 0) { /* if destruction succeeded */
|
|
event_name [last_index]++; /* then increment the event digit */
|
|
event_error = destroy_event (event_name, &lock_id); /* and destroy the second event */
|
|
}
|
|
|
|
if (event_error == 0) { /* if destruction succeeded */
|
|
event_name [last_index]++; /* then increment the event digit */
|
|
event_error = destroy_event (event_name, &unlock_id); /* and destroy the second event */
|
|
}
|
|
|
|
if (event_error == 0) /* if the destruction succeeded */
|
|
return SCPE_OK; /* then report success */
|
|
else /* otherwise */
|
|
return SCPE_INCOMP; /* report that the command did not complete */
|
|
}
|
|
|
|
|
|
/* Processor interconnect bootstrap loaders (special BBL and 12992K).
|
|
|
|
The special Basic Binary Loader (BBL) used by the 2000 Access system loads
|
|
absolute binary programs into memory from either the processor interconnect
|
|
interface or the paper tape reader interface. Two program entry points are
|
|
provided. Starting the loader at address x7700 loads from the processor
|
|
interconnect, while starting at address x7750 loads from the paper tape
|
|
reader. The S register setting does not affect loader operation.
|
|
|
|
For a 2100/14/15/16 CPU, entering a LOAD IPLI or BOOT IPLI command loads the
|
|
special BBL into memory and executes the processor interconnect portion
|
|
starting at x7700. Loader execution ends with one of the following halt
|
|
instructions:
|
|
|
|
* HLT 11 - a checksum error occurred; A/B = the calculated/tape value.
|
|
* HLT 55 - the program load address would overlay the loader.
|
|
* HLT 77 - the end of input with successful read; A = the paper tape select
|
|
code, B = the processor interconnect select code.
|
|
|
|
The 12992K boot loader ROM reads an absolute program from the processor
|
|
interconnect or paper tape interfaces into memory. The S register setting
|
|
does not affect loader operation. Loader execution ends with one of the
|
|
following halt instructions:
|
|
|
|
* HLT 11 - a checksum error occurred; A/B = the calculated/tape value.
|
|
* HLT 55 - the program load address would overlay the ROM loader.
|
|
* HLT 77 - the end of tape was reached with a successful read.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. After the BMDL has been loaded into memory, the paper tape portion may be
|
|
executed manually by setting the P register to the starting address
|
|
(x7750).
|
|
|
|
2. For compatibility with the "cpu_copy_loader" routine, the BBL device I/O
|
|
instructions address select code 10.
|
|
|
|
3. For 2000B, C, and F versions that use dual CPUs, the I/O Processor is
|
|
loaded with the standard BBL configured for the select codes of the
|
|
processor interconnect interface. 2000 Access must use the special BBL
|
|
because the paper tape reader is connected to the IOP in this version; in
|
|
prior versions, it was connected to the System Processor and could use
|
|
the paper-tape portion of the BMDL that was installed in the SP.
|
|
*/
|
|
|
|
static const LOADER_ARRAY ipl_loaders = {
|
|
{ /* HP 21xx 2000/Access special Basic Binary Loader */
|
|
000, /* loader starting index */
|
|
IBL_NA, /* DMA index */
|
|
073, /* FWA index */
|
|
{ 0163774, /* 77700: PI LDA 77774,I Processor Interconnect start */
|
|
0027751, /* 77701: JMP 77751 */
|
|
0107700, /* 77702: START CLC 0,C */
|
|
0002702, /* 77703: CLA,CCE,SZA */
|
|
0063772, /* 77704: LDA 77772 */
|
|
0002307, /* 77705: CCE,INA,SZA,RSS */
|
|
0027760, /* 77706: JMP 77760 */
|
|
0017736, /* 77707: JSB 77736 */
|
|
0007307, /* 77710: CMB,CCE,INB,SZB,RSS */
|
|
0027705, /* 77711: JMP 77705 */
|
|
0077770, /* 77712: STB 77770 */
|
|
0017736, /* 77713: JSB 77736 */
|
|
0017736, /* 77714: JSB 77736 */
|
|
0074000, /* 77715: STB 0 */
|
|
0077771, /* 77716: STB 77771 */
|
|
0067771, /* 77717: LDB 77771 */
|
|
0047773, /* 77720: ADB 77773 */
|
|
0002040, /* 77721: SEZ */
|
|
0102055, /* 77722: HLT 55 */
|
|
0017736, /* 77723: JSB 77736 */
|
|
0040001, /* 77724: ADA 1 */
|
|
0177771, /* 77725: STB 77771,I */
|
|
0037771, /* 77726: ISZ 77771 */
|
|
0000040, /* 77727: CLE */
|
|
0037770, /* 77730: ISZ 77770 */
|
|
0027717, /* 77731: JMP 77717 */
|
|
0017736, /* 77732: JSB 77736 */
|
|
0054000, /* 77733: CPB 0 */
|
|
0027704, /* 77734: JMP 77704 */
|
|
0102011, /* 77735: HLT 11 */
|
|
0000000, /* 77736: NOP */
|
|
0006600, /* 77737: CLB,CME */
|
|
0103700, /* 77740: STC 0,C */
|
|
0102300, /* 77741: SFS 0 */
|
|
0027741, /* 77742: JMP 77741 */
|
|
0106400, /* 77743: MIB 0 */
|
|
0002041, /* 77744: SEZ,RSS */
|
|
0127736, /* 77745: JMP 77736,I */
|
|
0005767, /* 77746: BLF,CLE,BLF */
|
|
0027740, /* 77747: JMP 77740 */
|
|
0163775, /* 77750: PTAPE LDA 77775,I Paper tape start */
|
|
0043765, /* 77751: CONFG ADA 77765 */
|
|
0073741, /* 77752: STA 77741 */
|
|
0043766, /* 77753: ADA 77766 */
|
|
0073740, /* 77754: STA 77740 */
|
|
0043767, /* 77755: ADA 77767 */
|
|
0073743, /* 77756: STA 77743 */
|
|
0027702, /* 77757: EOT JMP 77702 */
|
|
0063777, /* 77760: LDA 77777 */
|
|
0067776, /* 77761: LDB 77776 */
|
|
0102077, /* 77762: HLT 77 */
|
|
0027702, /* 77763: JMP 77702 */
|
|
0000000, /* 77764: NOP */
|
|
0102300, /* 77765: SFS 0 */
|
|
0001400, /* 77766: OCT 1400 */
|
|
0002500, /* 77767: OCT 2500 */
|
|
0000000, /* 77770: OCT 0 */
|
|
0000000, /* 77771: OCT 0 */
|
|
0177746, /* 77772: DEC -26 */
|
|
0100100, /* 77773: ABS -PI */
|
|
0077776, /* 77774: DEF *+2 */
|
|
0077777, /* 77775: DEF *+2 */
|
|
0000010, /* 77776: PISC OCT 10 */
|
|
0000010 } }, /* 77777: PTRSC OCT 10 */
|
|
|
|
{ /* HP 1000 Loader ROM (12992K) */
|
|
IBL_START, /* loader starting index */
|
|
IBL_DMA, /* DMA index */
|
|
IBL_FWA, /* FWA index */
|
|
{ 0107700, /* 77700: ST CLC 0,C ; intr off */
|
|
0002401, /* 77701: CLA,RSS ; skip in */
|
|
0063756, /* 77702: CN LDA M11 ; feed frame */
|
|
0006700, /* 77703: CLB,CCE ; set E to rd byte */
|
|
0017742, /* 77704: JSB READ ; get #char */
|
|
0007306, /* 77705: CMB,CCE,INB,SZB ; 2's comp */
|
|
0027713, /* 77706: JMP *+5 ; non-zero byte */
|
|
0002006, /* 77707: INA,SZA ; feed frame ctr */
|
|
0027703, /* 77710: JMP *-3 */
|
|
0102077, /* 77711: HLT 77B ; stop */
|
|
0027700, /* 77712: JMP ST ; next */
|
|
0077754, /* 77713: STA WC ; word in rec */
|
|
0017742, /* 77714: JSB READ ; get feed frame */
|
|
0017742, /* 77715: JSB READ ; get address */
|
|
0074000, /* 77716: STB 0 ; init csum */
|
|
0077755, /* 77717: STB AD ; save addr */
|
|
0067755, /* 77720: CK LDB AD ; check addr */
|
|
0047777, /* 77721: ADB MAXAD ; below loader */
|
|
0002040, /* 77722: SEZ ; E =0 => OK */
|
|
0027740, /* 77723: JMP H55 */
|
|
0017742, /* 77724: JSB READ ; get word */
|
|
0040001, /* 77725: ADA 1 ; cont checksum */
|
|
0177755, /* 77726: STA AD,I ; store word */
|
|
0037755, /* 77727: ISZ AD */
|
|
0000040, /* 77730: CLE ; force wd read */
|
|
0037754, /* 77731: ISZ WC ; block done? */
|
|
0027720, /* 77732: JMP CK ; no */
|
|
0017742, /* 77733: JSB READ ; get checksum */
|
|
0054000, /* 77734: CPB 0 ; ok? */
|
|
0027702, /* 77735: JMP CN ; next block */
|
|
0102011, /* 77736: HLT 11 ; bad csum */
|
|
0027700, /* 77737: JMP ST ; next */
|
|
0102055, /* 77740: H55 HLT 55 ; bad address */
|
|
0027700, /* 77741: JMP ST ; next */
|
|
0000000, /* 77742: RD NOP */
|
|
0006600, /* 77743: CLB,CME ; E reg byte ptr */
|
|
0103710, /* 77744: STC RDR,C ; start reader */
|
|
0102310, /* 77745: SFS RDR ; wait */
|
|
0027745, /* 77746: JMP *-1 */
|
|
0106410, /* 77747: MIB RDR ; get byte */
|
|
0002041, /* 77750: SEZ,RSS ; E set? */
|
|
0127742, /* 77751: JMP RD,I ; no, done */
|
|
0005767, /* 77752: BLF,CLE,BLF ; shift byte */
|
|
0027744, /* 77753: JMP RD+2 ; again */
|
|
0000000, /* 77754: WC 000000 ; word count */
|
|
0000000, /* 77755: AD 000000 ; address */
|
|
0177765, /* 77756: M11 DEC -11 ; feed count */
|
|
0000000, /* 77757: NOP */
|
|
0000000, /* 77760: NOP */
|
|
0000000, /* 77761: NOP */
|
|
0000000, /* 77762: NOP */
|
|
0000000, /* 77763: NOP */
|
|
0000000, /* 77764: NOP */
|
|
0000000, /* 77765: NOP */
|
|
0000000, /* 77766: NOP */
|
|
0000000, /* 77767: NOP */
|
|
0000000, /* 77770: NOP */
|
|
0000000, /* 77771: NOP */
|
|
0000000, /* 77772: NOP */
|
|
0000000, /* 77773: NOP */
|
|
0000000, /* 77774: NOP */
|
|
0000000, /* 77775: NOP */
|
|
0000000, /* 77776: NOP */
|
|
0100100 } } /* 77777: MAXAD ABS -ST ; max addr */
|
|
};
|
|
|
|
|
|
/* Device boot routine.
|
|
|
|
This routine is called directly by the BOOT IPLI and LOAD IPLI commands to
|
|
copy the device bootstrap into the upper 64 words of the logical address
|
|
space. It is also called indirectly by a BOOT CPU or LOAD CPU command when
|
|
the specified HP 1000 loader ROM socket contains a 12992K ROM.
|
|
|
|
When called in response to a BOOT IPLI or LOAD IPLI command, the "unitno"
|
|
parameter indicates the unit number specified in the BOOT command or is zero
|
|
for the LOAD command, and "dptr" points at the IPLI device structure.
|
|
Depending on the current CPU model, the special BBL or 12992K loader ROM will
|
|
be copied into memory and configured for the IPLI select code. If the CPU is
|
|
a 1000, the S register will be set as it would be by the front-panel
|
|
microcode.
|
|
|
|
When called for a BOOT/LOAD CPU command, the "unitno" parameter indicates the
|
|
select code to be used for configuration, and "dptr" will be NULL. As above,
|
|
the special BBL or 12992K loader ROM will be copied into memory and
|
|
configured for the specified select code. The S register is assumed to be
|
|
set correctly on entry and is not modified.
|
|
|
|
For the 12992K boot loader ROM, the S register will be set as follows:
|
|
|
|
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
| ROM # | 0 0 | IPLI select code | 0 0 0 0 0 0 |
|
|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
*/
|
|
|
|
static t_stat ipl_boot (int32 unitno, DEVICE *dptr)
|
|
{
|
|
static const HP_WORD ipl_ptx = 074u; /* the index of the pointer to the IPL select code */
|
|
static const HP_WORD ptr_ptx = 075u; /* the index of the pointer to the PTR select code */
|
|
static const HP_WORD ipl_scx = 076u; /* the index of the IPL select code */
|
|
static const HP_WORD ptr_scx = 077u; /* the index of the PTR select code */
|
|
uint32 start;
|
|
uint32 ptr_sc, ipl_sc;
|
|
DEVICE *ptr_dptr;
|
|
|
|
ptr_dptr = find_dev ("PTR"); /* get a pointer to the paper tape reader device */
|
|
|
|
if (ptr_dptr == NULL) /* if the paper tape device is not present */
|
|
return SCPE_IERR; /* then something is seriously wrong */
|
|
else /* otherwise */
|
|
ptr_sc = ((DIB *) ptr_dptr->ctxt)->select_code; /* get the select code from the device's DIB */
|
|
|
|
if (dptr == NULL) /* if we are being called for a BOOT/LOAD CPU command */
|
|
ipl_sc = (uint32) unitno; /* then get the select code from the "unitno" parameter */
|
|
else /* otherwise */
|
|
ipl_sc = ipli_dib.select_code; /* use the device select code from the DIB */
|
|
|
|
start = cpu_copy_loader (ipl_loaders, ipl_sc, /* copy the boot loader to memory */
|
|
IBL_S_NOCLEAR, IBL_S_NOSET); /* but do not alter the S register */
|
|
|
|
if (start == 0) /* if the copy failed */
|
|
return SCPE_NOFNC; /* then reject the command */
|
|
|
|
else { /* otherwise */
|
|
if (mem_examine (start + ptr_scx) <= SC_MAX) { /* if this is the special BBL */
|
|
mem_deposit (start + ipl_ptx, (HP_WORD) start + ipl_scx); /* then configure */
|
|
mem_deposit (start + ptr_ptx, (HP_WORD) start + ptr_scx); /* the pointers */
|
|
mem_deposit (start + ipl_scx, (HP_WORD) ipli_dib.select_code); /* and select codes */
|
|
mem_deposit (start + ptr_scx, (HP_WORD) ptr_sc); /* for the loader */
|
|
}
|
|
|
|
return SCPE_OK; /* the boot loader was successfully copied */
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* IPL device local utility routines */
|
|
|
|
|
|
|
|
/* Processor Interconnect service routine.
|
|
|
|
This routine is scheduled when the IPL is attached or an ioSTC signal is
|
|
received and is entered to check the Device Flag signals of the two interface
|
|
cards. The order in which the cards are checked is significant, as a prior
|
|
command sent via the output card must be acknowledged before a command from
|
|
the input card is recognized.
|
|
|
|
When a card's Device Flag signal is asserted, the routine saves the input
|
|
data present on the Data In bus and then sets the Flag Buffer flip-flop. It
|
|
then asserts the ENF signal to set the Flag flip-flop and then denies the
|
|
Device Command Out signal. If the output card is responding, the next poll
|
|
time is lengthened to ensure that the card status is read before checking the
|
|
input card for a command. Otherwise, the current poll time is reused.
|
|
|
|
If a flag signal was not seen, the current poll time is doubled, with upper
|
|
limits set by the interlock count and "poll_wait" setting. The poll is then
|
|
rescheduled if the cards are attached. Otherwise, the service call was for a
|
|
diagnostic test, where periodic polling is not done.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "uptr" parameter always points at the input card unit; the output
|
|
card unit has no separate service routine, as it is serviced here
|
|
concurrently with the input unit.
|
|
|
|
2. For rapid response during block data transfers, the poll wait wants to be
|
|
as short as possible. However, for reduced overhead, the poll wait wants
|
|
to be as long as possible. We solve this conflict by using an adaptable
|
|
poll wait. It starts at a delay of one event tick and doubles each time
|
|
a poll does not encounter an input condition until it reaches a preset
|
|
maximum limit. This gives good response to continuous transfers but
|
|
reduces the overhead between transfers.
|
|
|
|
3. If the IPL is operating synchronously, the poll wait is limited to
|
|
one-half of the interlock time. This ensures that a pending event will
|
|
be seen and processing begun within one execution quantum of its
|
|
occurrence. That is, if an instance transmits a word, the other instance
|
|
will receive and begin processing that word no later than 1.5 quanta
|
|
after transmission.
|
|
|
|
4. The CARD_INDEX enumeration is (ipli, iplo), so to loop in the reverse
|
|
order, we must cast the terminating condition to an "int" comparison, as
|
|
the integer type used to represent enumerations is implementation-
|
|
dependent. In particular, it may be an unsigned type.
|
|
|
|
5. The routine may be entered when the unit is not attached to manipulate
|
|
the local card stateinstead of the remote card on the other end of the
|
|
interonnecting cable. In this case, the shared memory region will not
|
|
exist, so the associated pointer must be validated befure use.
|
|
|
|
6. The other instance may have changed the interlock count without our
|
|
knowledge. Therefore, we check the interlock condition on entry and
|
|
schedule the synchronization unit if interlocking has been enabled. If
|
|
interlocking is disabled, we clear the synchronization unit's wait time
|
|
to indicate asynchronous operation (the unit will be inactivated after
|
|
its next entry).
|
|
|
|
7. 2000 Access has a race condition that causes a user program's printer or
|
|
paper tape punch output to stop for no apparent reason. The race occurs
|
|
more often when a large amount of output is generated quickly.
|
|
|
|
The cause is this code in subroutine #IPAL in the SP main program source
|
|
(STSB) that is used to send output data to a non-shareable device
|
|
controlled by the IOP:
|
|
|
|
LDA ERTMP SEND
|
|
IOR ALB REQUEST
|
|
JSB SDVRP,I CODE
|
|
LDB #IPAL RETRIEVE
|
|
INB BUFFER
|
|
LDA B,I LENGTH
|
|
JSB SDVRP,I SEND TO IOP
|
|
SFS CH2 WAIT FOR
|
|
JMP *-1 ACKNOWLEDGEMENT
|
|
CLF 0 INHIBIT INTERRUPTS
|
|
LIA CH2 RETRIEVE RESPONSE
|
|
|
|
If the Allocate Buffer ("ALB") request is refused by the IOP because all
|
|
output buffers are in use, the resulting "no buffer available" response
|
|
will cause the SP to issue a Release Buffers command to the IOP and then
|
|
suspend the user's program. When the line printer completes its
|
|
operation on the current output buffer, the IOP releases it and then
|
|
indicates buffer availability by sending a Wake Up User command to the SP
|
|
to retry the allocation request.
|
|
|
|
The problem occurs when the line printer finishes a line just as the
|
|
Allocate Buffer request is made. That request sends two words: the ALB
|
|
request code and the buffer length. After receiving the second word, the
|
|
IOP finds all buffers are in use and denies the request. If the line
|
|
printer completion then arrives, the IOP immediately sends a Wake Up User
|
|
command to indicate that a buffer is now available. If the command
|
|
arrives between the time the SP sends the buffer length word and the time
|
|
it retrieves the response, the user program will hang. This occurs
|
|
because the "no buffer available" response subsequently causes the SP to
|
|
set a flag to indicate that the user has been suspended for buffer
|
|
availability. If the Wake Up User command arrives before that flag is
|
|
set, the command is ignored. So the SP is waiting for the IOP to issue
|
|
the command, but the IOP has already issued it. This leaves the
|
|
suspension in force until the user aborts the program by pressing the
|
|
BREAK key.
|
|
|
|
Nominally, there are only about nine instructions executed between the
|
|
STC CH2 (within the SDVRP subroutine) that causes an IOP to accept the
|
|
buffer length word and the SFS CH2 (above) that detects that the IOP's
|
|
acknowledgement has arrived. Once detected, the CLF 0 instruction turns
|
|
the interrupt system off so that commands received from the IOP are
|
|
deferred until after the user suspension flag is set.
|
|
|
|
However, the SP's interrupt system is on during the above SFS CH2/JMP *-1
|
|
loop, and the Processor Interconnect has the highest I/O interrupt
|
|
priority. So if, say, a time-base generator interrupt occurs during the
|
|
SFS loop, several dozen instructions may be executed before control
|
|
returns to the loop. If, during that time, the IOP command arrives, the
|
|
resulting higher-priority interrupt is handled before the loop return,
|
|
and the command is ignored because the "user is suspended" flag has not
|
|
been set.
|
|
|
|
We work around this problem by arranging the card service routine to
|
|
ensure that the IOP status response is picked up before an IOP command if
|
|
they both are seen during the same input poll. However, there is no
|
|
general way to ensure that the response is processed by the SP program
|
|
before a pending IOP command is recognized. That is because the SP does
|
|
not read the responses of some commands it sends, so simply holding off
|
|
input card commands until the output card data register is read will not
|
|
work.
|
|
|
|
The best we can do to reduce the frequency of the race condition is to
|
|
delay IOP command recognition after a status response arrives. We do
|
|
this by rescheduling the poll using a delay of ten times the normal
|
|
maximum poll delay to give any intervening interrupt handlers time to
|
|
complete. The next STC directed to either card clears the delay and
|
|
reschedules the poll for immediate entry, providing rapid response when
|
|
block data is being transferred in either direction across the Processor
|
|
Interconnect.
|
|
*/
|
|
|
|
static t_stat card_service (UNIT *uptr)
|
|
{
|
|
static uint32 delta = 0; /* accumulated time between receptions */
|
|
CARD_INDEX card;
|
|
t_stat status = SCPE_OK;
|
|
|
|
tprintf (ipli_dev, TRACE_PSERV, "Poll delay %d service entered\n",
|
|
uptr->wait);
|
|
|
|
if (shared_ptr != NULL) /* if the shared memory region exists */
|
|
if (shared_ptr->count == 0) /* then if interlocking is disabled */
|
|
sync_unit.wait = 0; /* then ensure that we are in asynchronous mode */
|
|
|
|
else if (sync_unit.wait == 0) /* otherwise if we should be interlocked but are not */
|
|
activate_unit (&sync_unit, 1); /* then activate the synchronizer immediately */
|
|
|
|
delta = delta + uptr->wait; /* update the accumulated time */
|
|
|
|
for (card = iplo; (int) card >= (int) ipli; card--) /* process IPLO then IPLI in descending order */
|
|
if (io_ptrs [card].input->device_flag_in == TRUE) { /* if the Device Flag is asserted */
|
|
io_ptrs [card].input->device_flag_in = FALSE; /* then clear it */
|
|
|
|
ipl [card].input_word = io_ptrs [card].input->data_in; /* read the data input lines */
|
|
|
|
tpprintf (dptrs [card], TRACE_XFER, "Word %06o delta %u received from link\n",
|
|
ipl [card].input_word, delta);
|
|
|
|
ipl [card].flag_buffer = SET; /* set the flag buffer */
|
|
io_assert (dptrs [card], ioa_ENF); /* and flag flip-flops */
|
|
|
|
io_ptrs [card].output->device_command_out = FALSE; /* reset Device Command */
|
|
|
|
delta = 0; /* clear the accumulated time */
|
|
|
|
if (card == iplo) { /* if the output card received a status reply */
|
|
uptr->wait = poll_wait * 10; /* then schedule a longer wait to allow for status pickup */
|
|
break; /* before checking for an inbound command */
|
|
}
|
|
}
|
|
|
|
if (delta > 0) { /* if both Device Flags were denied */
|
|
uptr->wait = uptr-> wait * 2; /* then double the wait time for the next check */
|
|
|
|
if (sync_unit.wait > 0 /* if operating synchronously and the poll wait */
|
|
&& uptr->wait >= (int32) shared_ptr->count / 2) /* is longer than half an execution quantum */
|
|
uptr->wait = (int32) shared_ptr->count / 2; /* then shorten it to guarantee a response */
|
|
|
|
if (uptr->wait > poll_wait) /* if the new time is greater than the maximum time */
|
|
uptr->wait = poll_wait; /* then limit it to the maximum */
|
|
|
|
if (io_ptrs [ipli].input->cable_connected == FALSE /* if the interconnecting cable is not present */
|
|
&& cpu_io_stop (uptr)) /* and the I/O error stop is enabled */
|
|
status = STOP_NOCONN; /* then report the disconnection */
|
|
}
|
|
|
|
if (uptr->flags & UNIT_ATT) /* if the link is active */
|
|
activate_unit (uptr, uptr->wait); /* then continue to poll for input */
|
|
|
|
return status; /* return the event service status */
|
|
}
|
|
|
|
|
|
/* Simulator interlock service routine.
|
|
|
|
This routine is scheduled when instruction interlocking between two simulator
|
|
instance is desired. The service time is the count of instructions to be
|
|
executed between rendezvous attempts. When interlocking is disabled,
|
|
servicing is canceled.
|
|
|
|
On entry, the unit is reactivated if interlocking is still enabled. Then the
|
|
gate in the shared memory area is locked with an atomic operation. If the
|
|
gate was unlocked at the time, this instance waits until the other instance
|
|
unlocks the gate. Then the unlocking is acknowledged, releasing the other
|
|
instance to continue execution. The service routine then returns to allow
|
|
another set of instructions to be executed before the next interlock check.
|
|
|
|
If the gate was locked at the time of the check, the other instance is
|
|
released from its lock loop. Then the routine waits until the other instance
|
|
has confirmed that it has dropped out of its wait loop. This prevents the
|
|
situation of this instance unlocking and then reentering and locking the gate
|
|
while the other instance has been preempted in its wait loop, which leads to
|
|
deadlock, as both instances are waiting at the locked gate.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Interlocking may be canceled by the other instance while our service
|
|
routine is scheduled. So we may be entered with the interlock count
|
|
equal to zero. If this occurs, the routine exits without rescheduling.
|
|
*/
|
|
|
|
static t_stat sync_service (UNIT *uptr)
|
|
{
|
|
t_stat status;
|
|
volatile GATE_STATE *gate_ptr = &shared_ptr->gate; /* must be volatile to ensure an updated value */
|
|
|
|
tprintf (iplo_dev, TRACE_PSERV, "Synchronizer delay %d service entered with gate %s\n",
|
|
uptr->wait, gate_state_names [*gate_ptr]);
|
|
|
|
if (shared_ptr->count == 0) { /* if synchronization was canceled */
|
|
sync_unit.wait = 0; /* then enter asynchronous mode */
|
|
|
|
tprintf (iplo_dev, TRACE_PSERV, "Synchronizer stopped\n");
|
|
|
|
return SCPE_OK; /* return immediately */
|
|
}
|
|
|
|
else /* otherwise */
|
|
activate_unit (uptr, shared_ptr->count); /* reactivate for the next cycle */
|
|
|
|
if (sim_shmem_atomic_cas ((int32 *) &shared_ptr->gate, /* if the gate is unlocked */
|
|
Unlocked, Locked)) { /* then lock it and continue */
|
|
|
|
tprintf (iplo_dev, TRACE_PSERV, "Synchronizer locked\n");
|
|
|
|
status = wait_at_gate (lock_id, Locked, Unlocking); /* wait until the gate is unlocked */
|
|
release_wait (unlock_id, Unlocking, Unlocked); /* and then acknowledge the unlock */
|
|
}
|
|
|
|
else { /* otherwise the gate was locked on entry */
|
|
tprintf (iplo_dev, TRACE_PSERV, "Synchronizer unlocking\n");
|
|
|
|
release_wait (lock_id, Locked, Unlocking); /* unlock the other instance */
|
|
status = wait_at_gate (unlock_id, Unlocking, Unlocked); /* and wait until the unlock is acknowledged */
|
|
}
|
|
|
|
return status; /* return the service status */
|
|
}
|
|
|
|
|
|
/* Wait until a release event occurs.
|
|
|
|
This routine is called to suspend execution of the current instance until the
|
|
other instance signals that it is to continue. If, when the routine is
|
|
called, the gate is still in the state specified by the "initial" parameter,
|
|
execution enters a compute-bound loop to wait for the state to change. This
|
|
permits a speedy exit as soon as the gate value changes. However, if the
|
|
change does not occur within a specified number of iterations, the loop is
|
|
changed to a timed event wait. This permits the instance to yield processor
|
|
time, which is especially important if the other iteration is competing for
|
|
the same processor.
|
|
|
|
If the wait times out, the keyboard is polled for a CTRL+E. If one is seen,
|
|
the user has aborted the wait, and the simulator is stopped. If any other
|
|
key is pressed, or no key is pressed, the loop continues until a signal is
|
|
received from the other instance.
|
|
|
|
If the loop terminates due to the gate status changing, the loop statistics
|
|
are updated, and the routine returns SCPE_OK. If the loop was aborted, then
|
|
the gate state is checked. If the abort occurred after the loop transitioned
|
|
to event waiting but before the other instance had a chance to signal, then
|
|
no cleanup action is required, and the gate is reset to the "Unlocked" state.
|
|
However, if the other instance has signaled (or is about to) by changing the
|
|
gate to the "final" parameter state value, then an event wait is performed to
|
|
absorb the signal. This wait will always occur, so a wait timeout is not
|
|
used.
|
|
|
|
An abort may occur for four reasons. First, the user may abort with CTRL+E.
|
|
Second, an event error may occur. Third, the other instance may terminate
|
|
synchronous operation by executing a SET IPL INTERLOCK=0 command. Fourth,
|
|
the other instance may disconnect its cables (by detaching the device).
|
|
After cleaning up as necessary, the reason for the abort is returned.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The gate state must include an indication of whether the wait has
|
|
transitioned from a compute-bound loop to a timed-wait loop, so that the
|
|
other instance knows whether to signal the event as well as changing the
|
|
gate state. This is done by incrementing the initial gate state value.
|
|
The state values are arranged so that the event-wait states are single
|
|
increments of the possible initial values (e.g., the "Locked_Wait" state
|
|
value is one more than the "Locked" state value).
|
|
|
|
2. The gate must be declared "volatile" to ensure that it is reloaded for
|
|
each iteration of the wait loop. We cannot declare the variable itself
|
|
volatile, as the atomic compare-and-swap routine takes a non-volatile
|
|
variable as a parameter.
|
|
|
|
3. Detaching the cable also unlocks the gate, so no special test for a
|
|
disconnected cable is required.
|
|
|
|
4. It would be nice to call "sim_poll_kbd" to poll for a user abort so that
|
|
a CTRL+E produces the "scp>" prompt, rather than aborting execution in
|
|
addition to the wait. However, that routine supplies REPLY strings, and
|
|
so a wait with a pending REPLY will lose characters. So we have to use
|
|
"sim_os_poll_kbd" which does not affect REPLYs but also causes an
|
|
immediate simulator stop.
|
|
*/
|
|
|
|
static t_stat wait_at_gate (EVENT event_id, GATE_STATE initial, GATE_STATE final)
|
|
{
|
|
const uint32 wait_limit = 2000; /* the count at which to shift to event waiting */
|
|
const uint32 wait_time = 100; /* the event wait time in milliseconds */
|
|
t_bool signaled = FALSE;
|
|
uint32 iterations = 0;
|
|
t_stat status = SCPE_OK;
|
|
volatile GATE_STATE *gate_ptr = &shared_ptr->gate; /* must be volatile to ensure an updated value */
|
|
|
|
while (*gate_ptr == initial) { /* while waiting for the gate to leave the initial state */
|
|
iterations = iterations + 1; /* increment the iteration counter */
|
|
|
|
if (iterations == wait_limit /* if the wait limit has been reached */
|
|
&& sim_shmem_atomic_cas ((int32 *) &shared_ptr->gate, /* and changing from the initial */
|
|
initial, initial + 1)) /* to the waiting state succeeds */
|
|
do { /* then wait in a loop */
|
|
event_error = wait_event (event_id, wait_time, /* for the event */
|
|
&signaled); /* to be signaled */
|
|
|
|
if (event_error) /* if the wait failed */
|
|
status = SCPE_IERR; /* then indicate an internal error */
|
|
|
|
else if (signaled == FALSE) { /* otherwise if the wait timed out */
|
|
iterations = iterations + 1; /* then increment the iteration counter */
|
|
status = sim_os_poll_kbd (); /* and check the keyboard for a user stop */
|
|
|
|
if (status >= SCPE_KFLAG) /* if a key was pressed */
|
|
status = SCPE_OK; /* then ignore it */
|
|
else if (stop_cpu) /* otherwise if a signal was received */
|
|
status = SCPE_STOP; /* then stop the wait */
|
|
}
|
|
}
|
|
while (! signaled && status == SCPE_OK); /* loop if not signaled and no error */
|
|
}
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Synchronizer %s with gate %s\n",
|
|
(iterations <= wait_limit ? "resumed"
|
|
: (signaled ? "signaled" : "aborted")),
|
|
gate_state_names [*gate_ptr]);
|
|
|
|
if (status == SCPE_OK) { /* if the gate is transitioning */
|
|
if (iterations > sync_max) /* then reset the maximum iteration count */
|
|
sync_max = iterations; /* if this pass was larger */
|
|
|
|
sync_cnt = sync_cnt + 1; /* increment the pass counter */
|
|
|
|
sync_mean = sync_mean /* calculate the running */
|
|
+ ((float) iterations - sync_avg) / sync_cnt; /* average wait time */
|
|
|
|
sync_avg = (uint32) sync_mean; /* round it and save for inspection */
|
|
}
|
|
|
|
else if (sim_shmem_atomic_cas ((int32 *) &shared_ptr->gate, /* otherwise the wait was aborted */
|
|
initial + 1, Unlocked) == FALSE /* and if not yet waiting for the event */
|
|
&& *gate_ptr == final) /* but the gate was transitioning */
|
|
event_error = wait_event (event_id, INFINITE, /* then wait for the signal */
|
|
&signaled); /* that must occur */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Synchronizer transitioning after %u iterations with gate %s\n",
|
|
iterations, gate_state_names [*gate_ptr]);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/* Release the gate.
|
|
|
|
This routine is called to release the other instance after it has arrived at
|
|
the rendezvous point. If the other instance is in its compute-bound loop, as
|
|
indicated by the gate equalling the "initial" parameter state value, then the
|
|
gate is simply set to the "final" parameter state value. However, if it is
|
|
in its event-wait loop, indicated by the gate equalling the state after the
|
|
initial state, then in addition the event specified by the "event_id"
|
|
parameter is signaled. When the routine returns, the rendezvous is complete,
|
|
and both instances continue to execute machine instructions.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. While the gate state transitions are implemented by atomic operations,
|
|
there is a potential race point between the change from the "initial with
|
|
wait" state to the "final" state and the signaling of the event. If the
|
|
wait loop in the other instance aborts at that point, the gate state will
|
|
indicate that a signal is coming, but it has not actually occurred yet.
|
|
|
|
To address this, the "wait_at_gate" routine always waits for the signal
|
|
in this case, so that the signal is absorbed, regardless of whether it
|
|
occurs before or after the wait call.
|
|
*/
|
|
|
|
static void release_wait (EVENT event_id, GATE_STATE initial, GATE_STATE final)
|
|
{
|
|
volatile GATE_STATE *gate_ptr = &shared_ptr->gate; /* must be volatile to ensure an updated value */
|
|
|
|
if (sim_shmem_atomic_cas ((int32 *) &shared_ptr->gate, /* if the other process */
|
|
initial, final) == FALSE) /* is waiting */
|
|
if (sim_shmem_atomic_cas ((int32 *) &shared_ptr->gate, /* for the release event */
|
|
initial + 1, final)) { /* to occur before proceeding */
|
|
event_error = signal_event (event_id); /* then signal the event */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Synchronizer signaling the release event with gate %s\n",
|
|
gate_state_names [*gate_ptr]);
|
|
}
|
|
|
|
else { /* otherwise the gate state is unexpected */
|
|
*gate_ptr = final; /* so transition immediately to the final state */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Synchronizer gate should be %s or %s but is %s\n",
|
|
gate_state_names [initial], gate_state_names [initial + 1],
|
|
gate_state_names [*gate_ptr]);
|
|
}
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Synchronizer releasing with gate %s\n",
|
|
gate_state_names [*gate_ptr]);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Activate a unit.
|
|
|
|
The specified unit is added to the event queue with the delay specified by
|
|
the unit wait field.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. This routine may be called with wait = 0, which will expire immediately
|
|
and enter the service routine with the next sim_process_event call.
|
|
Activation is required in this case to allow the service routine to
|
|
return an error code to stop the simulation. If the service routine was
|
|
called directly, any returned error would be lost.
|
|
*/
|
|
|
|
static void activate_unit (UNIT *uptr, int32 wait_time)
|
|
{
|
|
static const char *unit_name [CARD_COUNT] = { "Poll", "Synchronizer" };
|
|
const CARD_INDEX card = (CARD_INDEX) (uptr == &sync_unit); /* set card selector */
|
|
|
|
tpprintf (dptrs [card], TRACE_PSERV, "%s delay %u service scheduled\n",
|
|
unit_name [card], wait_time);
|
|
|
|
uptr->wait = wait_time; /* save the specified wait */
|
|
sim_activate (uptr, uptr->wait); /* and activate the unit */
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/* Handler for the CTRL+E signal.
|
|
|
|
This handler is installed while executing a SET IPL WAIT command. It is
|
|
called if the user presses CTRL+E on a UNIX host to abort the wait command.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. On Windows hosts, SIGINT is bound to CTRL+C and cannot be remapped as it
|
|
can under UNIX. So CTRL+E is delivered through the keyboard poll as a
|
|
normal character. Moreover, in raw mode, CTRL+C is also delivered
|
|
through the keyboard poll, and this handler is never called.
|
|
*/
|
|
|
|
static void wru_handler (int signal)
|
|
{
|
|
wait_aborted = TRUE; /* the user has aborted the event wait */
|
|
return;
|
|
}
|
|
|
|
|
|
/* Trace a TSB command.
|
|
|
|
This routine is called to decode and trace a command issued by the SP or IOP
|
|
instance running HP 2000 Time-Shared BASIC. It also traces command
|
|
parameters sent on the same channel.
|
|
|
|
On entry, the "card" parameter indicates which I/O card is handling the
|
|
command, "command" contains the command or parameter word, and "response"
|
|
contains the current response state. If the response state is "None", then
|
|
"command" represents a command word. Otherwise, it represents a command
|
|
parameter word sent after the command.
|
|
|
|
For the command case, the "card" and "cpu_is_iop" values determine whether an
|
|
SP command or an IOP command is being issued. On the SP instance, SP
|
|
commands are sent on the output card, and IOP commands are received on the
|
|
input card. On the IOP instance, the commands are reversed.
|
|
|
|
The entry in the command descriptor table is determined initially by decoding
|
|
the primary opcode. If the command uses subopcodes (opcode = 7), the index
|
|
is adjusted by decoding the subopcode and adding it and the table offset. If
|
|
the command comes from the IOP, the index is adjusted to the IOP section of
|
|
the descriptor table.
|
|
|
|
If the TSB version is not Access, the index is remapped to point at the
|
|
corresponding 2000F descriptor entry. Otherwise, if the Access opcode has an
|
|
opcode extension field, then adjust the index by decoding the extension and
|
|
adding it and the extension section offset.
|
|
|
|
With the table index finalized, if the command name is undefined, the command
|
|
is unused, and nothing is printed. Otherwise, the mask value is examined to
|
|
determine the size and alignment of the first and second operand fields. If
|
|
the second operand is not defined, the operand value is cleared in case the
|
|
command contains extraneous bits.
|
|
|
|
Finally, the decoded command and operands are printed. The "Process output
|
|
character" command (opcode 0) is the only one whose second operand is a
|
|
character rather than a numeric value, so a separate print statement is used
|
|
for this case. For the other cases, inclusion or omission of the first and
|
|
second operand labels and values is accomplished by changing the precision of
|
|
the corresponding format specifications. After printing, the next expected
|
|
response is returned to permit any following command parameter or returned
|
|
status values to be printed properly.
|
|
|
|
Command parameters and DMA transfers are handled as directed by the
|
|
"response" value. DMA transfers continue until ended when the EDT signal
|
|
arrives at the interface. Entering the routine with an unexpected response
|
|
produces an appropriate diagnostic before returning "None" to reset for the
|
|
next expected command.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. DMA character transfers must be masked to 7 bits. Some transfers, such
|
|
as that following a "Process output string" command to print the program
|
|
name, include characters with the high bit set. These are stripped off
|
|
for ease of trace interpretation.
|
|
*/
|
|
|
|
static RESPONSE trace_command (CARD_INDEX card, HP_WORD command, RESPONSE response)
|
|
{
|
|
uint32 index, operand_1, operand_2;
|
|
|
|
switch (response) { /* dispatch on the current response state */
|
|
|
|
case None: /* no prior response; command expected */
|
|
index = CM_OPCODE (command); /* decode the primary opcode */
|
|
|
|
if (index == SUBOP_OPCODE) /* if this command contains a subopcode */
|
|
index = CM_SUBOP (command) + SUBOP_OFFSET; /* then decode it and offset to the proper section */
|
|
|
|
if (cpu_is_iop ^ (card == ipli)) /* if an IOP command is expected */
|
|
index = index + IOP_OFFSET; /* then offset to the IOP section */
|
|
|
|
if (shared_ptr->tsb_version == HP_2000F) /* if running 2000F TSB */
|
|
index = remap_2000F [index]; /* then remap to the equivalent 2000F command */
|
|
|
|
else if (index == SUBOP_OFFSET) /* otherwise if this is an extended command */
|
|
index = index + CM_EXTOP (command) + EXTOP_OFFSET; /* then offset to the extension section */
|
|
|
|
if (cmd [index].name != NULL) { /* if the entry is assigned */
|
|
if (cmd [index].mask == 0176000) { /* then if it has a 6-bit first operand */
|
|
operand_1 = (command & cmd [index].mask) >> 7; /* then mask and shift it into place */
|
|
operand_2 = command & 0177; /* and mask the second operand to 7 bits */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
operand_1 = (command & cmd [index].mask) >> 8; /* mask and shift the first operand into place */
|
|
operand_2 = command & 0377; /* and mask the second operand to 8 bits */
|
|
}
|
|
|
|
if (cmd [index].low_label [0] == '\0') /* if there is no second operand */
|
|
operand_2 = 0; /* then clear the value of extraneous bits */
|
|
|
|
if (index == 0) /* if this is the POC command */
|
|
hp_trace (dptrs [card], TRACE_CMD, "%s command%s%u%s%s\n", /* then format a character operand */
|
|
cmd [index].name,
|
|
cmd [index].high_label,
|
|
operand_1,
|
|
cmd [index].low_label,
|
|
fmt_char (operand_2));
|
|
else /* otherwise format a numeric operand */
|
|
hp_trace (dptrs [card], TRACE_CMD, "%s command%s%.*u%s%.*u\n",
|
|
cmd [index].name, /* print the command name */
|
|
cmd [index].high_label, /* print the first operand label */
|
|
(cmd [index].high_label [0] != '\0'), /* and the operand value */
|
|
operand_1, /* if the label is not empty */
|
|
cmd [index].low_label, /* print the second operand label */
|
|
(cmd [index].low_label [0] != '\0'), /* and the operand value */
|
|
operand_2); /* if the label is not empty */
|
|
}
|
|
|
|
return cmd [index].response;
|
|
|
|
|
|
case DMA_Octal:
|
|
hp_trace (dptrs [card], TRACE_CMD, "DMA transfer %06o sent\n", command);
|
|
return DMA_Octal;
|
|
|
|
|
|
case DMA_Chars:
|
|
hp_trace (dptrs [card], TRACE_CMD, "DMA transfer %06o (%s, %s) sent\n",
|
|
command,
|
|
fmt_char (UPPER_BYTE (command) & DATA_MASK),
|
|
fmt_char (LOWER_BYTE (command) & DATA_MASK));
|
|
return DMA_Chars;
|
|
|
|
|
|
case Dec_Status:
|
|
case Dec_Stat_DMAC:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Sent data is %d\n", SEXT16 (command));
|
|
|
|
if (response == Dec_Status) /* after printing a decimal value */
|
|
return Status; /* the next word will be status */
|
|
else /* or status followed by */
|
|
return Status_DMAC; /* a DMA character transfer */
|
|
|
|
|
|
case Octal_DMAB:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Sent data is %06o\n", command);
|
|
return DMA_Octal;
|
|
|
|
|
|
case Character:
|
|
case Decimal:
|
|
case Octal:
|
|
case Status:
|
|
case Status_DMAC:
|
|
case Stat_Dec_DMAC:
|
|
case Decimal_DMAC:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Unexpected data %06o sent\n", command);
|
|
break;
|
|
} /* all cases are handled */
|
|
|
|
return None; /* return None here to catch the "others" case */
|
|
}
|
|
|
|
|
|
/* Trace a TSB status return.
|
|
|
|
This routine is called to decode and trace status or data words returned by
|
|
the SP or IOP instance running HP 2000 Time-Shared BASIC.
|
|
|
|
On entry, the "card" parameter indicates which I/O card is returning the
|
|
data, "status" contains the status or data word, and "response" contains the
|
|
current response state. If the response state is "None", then "status"
|
|
represents an unexpected return. Otherwise, it represents a status or data
|
|
word sent in response to a command.
|
|
|
|
Status, data words, and DMA transfers are handled as directed by the
|
|
"response" value. DMA transfers continue until ended when the EDT signal
|
|
arrives at the interface. Entering the routine with an unexpected response
|
|
produces an appropriate diagnostic before returning "None" to reset for the
|
|
next expected command.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The Access commands Echo On and Echo Off return the updated receive
|
|
parameter with the echo bit added or removed from the IOP to the SP.
|
|
This is contrary to the "Internal Maintenance Specifications" section of
|
|
the "HP 2000 Computer System Sources and Listings Documentation"
|
|
(22687-90020), which makes no mention of return values for these
|
|
commands. The SP ignores the values, but tracing will print "Unexpected
|
|
value returned" for these commands.
|
|
|
|
2. DMA character transfers must be masked to 7 bits. Some transfers, such
|
|
as that following a "Process output string" command to print the program
|
|
name, include characters with the high bit set. These are stripped off
|
|
for ease of trace interpretation.
|
|
*/
|
|
|
|
static RESPONSE trace_status (CARD_INDEX card, HP_WORD status, RESPONSE response)
|
|
{
|
|
int32 value;
|
|
|
|
switch (response) { /* dispatch on the current response state */
|
|
|
|
case Character:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Returned character is %s\n",
|
|
fmt_char ((uint8) status));
|
|
break;
|
|
|
|
|
|
case Decimal:
|
|
case Decimal_DMAC:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Returned data is %d\n", SEXT16 (status));
|
|
|
|
if (response == Decimal_DMAC) /* after printing a decimal value */
|
|
return DMA_Chars; /* a DMA character transfer follows */
|
|
break;
|
|
|
|
|
|
case Octal:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Returned data is %06o\n", status);
|
|
break;
|
|
|
|
|
|
case DMA_Octal:
|
|
hp_trace (dptrs [card], TRACE_CMD, "DMA transfer %06o returned\n", status);
|
|
return DMA_Octal;
|
|
|
|
|
|
case DMA_Chars:
|
|
hp_trace (dptrs [card], TRACE_CMD, "DMA transfer %06o (%s, %s) returned\n",
|
|
status,
|
|
fmt_char (UPPER_BYTE (status) & DATA_MASK),
|
|
fmt_char (LOWER_BYTE (status) & DATA_MASK));
|
|
return DMA_Chars;
|
|
|
|
|
|
case Status:
|
|
case Status_DMAC:
|
|
case Stat_Dec_DMAC:
|
|
value = SEXT16 (status);
|
|
|
|
if (value >= -3 && value <= 4)
|
|
hp_trace (dptrs [card], TRACE_CMD, "Returned status is %s\n",
|
|
status_names [value + STATUS_BIAS]);
|
|
else
|
|
hp_trace (dptrs [card], TRACE_CMD, "Returned status is %d\n", value);
|
|
|
|
if (response == Status_DMAC && value == 0)
|
|
return DMA_Chars;
|
|
|
|
else if (response == Stat_Dec_DMAC && value == 0)
|
|
return Decimal_DMAC;
|
|
break;
|
|
|
|
|
|
case None:
|
|
hp_trace (dptrs [card], TRACE_CMD, "Unexpected data %06o returned\n", status);
|
|
break;
|
|
|
|
|
|
case Octal_DMAB:
|
|
case Dec_Status:
|
|
case Dec_Stat_DMAC:
|
|
break; /* these responses only occur on output */
|
|
} /* all cases are handled */
|
|
|
|
return None; /* return None here to catch the "others" case */
|
|
}
|
|
|
|
|
|
|
|
/* Process synchronization routines */
|
|
|
|
|
|
|
|
#if defined (_WIN32) && ! defined (USE_FALLBACK)
|
|
|
|
/* Windows process synchronization */
|
|
|
|
|
|
/* Create a synchronization event.
|
|
|
|
This routine creates a synchronization event using the supplied name and
|
|
returns the event handle to the caller. If creation succeeds, the routine
|
|
returns 0. Otherwise, the error value is returned.
|
|
|
|
The event is created with these attributes: no security, automatic reset, and
|
|
initially non-signaled.
|
|
*/
|
|
|
|
static uint32 create_event (const char *name, EVENT *event)
|
|
{
|
|
uint32 error = 0;
|
|
|
|
*event = CreateEvent (NULL, FALSE, FALSE, name); /* create an auto-reset, initially not-signaled event */
|
|
|
|
if (*event == NULL) { /* if event creation failed */
|
|
error = (uint32) GetLastError (); /* then get the error code */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Creation with identifier \"%s\" failed with error %u\n",
|
|
name, error);
|
|
}
|
|
|
|
else /* otherwise the creation succeeded */
|
|
tprintf (iplo_dev, TRACE_STATE, "Created event %p with identifier \"%s\"\n",
|
|
(void *) *event, name);
|
|
|
|
return error; /* return the operation status */
|
|
}
|
|
|
|
|
|
/* Destroy a synchronization event.
|
|
|
|
This routine destroys the synchronization event specified by the supplied
|
|
event handle. If destruction succeeds, the event handle is invalidated, and
|
|
the routine returns 0. Otherwise, the error value is returned.
|
|
|
|
The event name parameter is not used but is present for interoperability.
|
|
*/
|
|
|
|
static uint32 destroy_event (const char *name, EVENT *event)
|
|
{
|
|
BOOL status;
|
|
uint32 error = 0;
|
|
|
|
if (*event != NULL) { /* if the event exists */
|
|
status = CloseHandle (*event); /* then close it */
|
|
|
|
if (status == FALSE) { /* if the close failed */
|
|
error = (uint32) GetLastError (); /* then get the error code */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Destruction of event %p failed with error %u\n",
|
|
(void *) *event, error);
|
|
}
|
|
|
|
else /* otherwise the destruction succeeded */
|
|
tprintf (iplo_dev, TRACE_STATE, "Destroyed event %p\n",
|
|
(void *) *event);
|
|
|
|
*event = NULL; /* reset the event handle */
|
|
}
|
|
|
|
return error; /* return the operation status */
|
|
}
|
|
|
|
|
|
/* Wait for a synchronization event.
|
|
|
|
This routine waits for a synchronization event to be signaled or for the
|
|
supplied maximum wait time to elapse. If the event identified by the
|
|
supplied handle is signaled, the routine returns 0 and sets the "signaled"
|
|
flag to TRUE. If the timeout expires without the event being signaled, the
|
|
routine returns 0 with the "signaled" flag set to FALSE. If the event wait
|
|
fails, the routine returns the error value.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The maximum wait time may be zero to test the signaled state and return
|
|
immediately, or may be set to "INFINITE" to wait forever. The latter is
|
|
not recommended, as it provides the user with no means to cancel the wait
|
|
and return to the SCP prompt.
|
|
*/
|
|
|
|
static uint32 wait_event (EVENT event, uint32 wait_in_ms, t_bool *signaled)
|
|
{
|
|
const DWORD wait_time = (DWORD) wait_in_ms; /* interval wait time in milliseconds */
|
|
DWORD status;
|
|
uint32 error = 0;
|
|
|
|
status = WaitForSingleObject (event, wait_time); /* wait for the event, but not forever */
|
|
|
|
if (status == WAIT_FAILED) { /* if the wait failed */
|
|
error = (uint32) GetLastError (); /* then get the error code */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Wait for event %p failed with error %u\n",
|
|
(void *) event, error);
|
|
}
|
|
|
|
else { /* otherwise the wait completed */
|
|
*signaled = (status != WAIT_TIMEOUT); /* so set the flag TRUE if the wait did not time out */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Event %p wait %s\n",
|
|
(void *) event, (*signaled ? "signaled" : "timed out"));
|
|
}
|
|
|
|
return error; /* return the operation status */
|
|
}
|
|
|
|
|
|
/* Signal the synchronization event.
|
|
|
|
This routine signals a the synchronization event specified by the supplied
|
|
event handle. If signaling succeeds, the routine returns 0. Otherwise, the
|
|
error value is returned.
|
|
*/
|
|
|
|
static uint32 signal_event (EVENT event)
|
|
{
|
|
BOOL status;
|
|
uint32 error = 0;
|
|
|
|
status = SetEvent (event); /* signal the event */
|
|
|
|
if (status == FALSE) { /* if the call failed */
|
|
error = (uint32) GetLastError (); /* then get the error code */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "Signal of event %p failed with error %u\n",
|
|
(void *) event, error);
|
|
}
|
|
|
|
else /* otherwise the signal succeeded */
|
|
tprintf (iplo_dev, TRACE_STATE, "Event %p signaled\n",
|
|
(void *) event);
|
|
|
|
return error; /* return the operation status */
|
|
}
|
|
|
|
|
|
|
|
#elif defined (HAVE_SEMAPHORE) && ! defined (USE_FALLBACK)
|
|
|
|
/* UNIX process synchronization */
|
|
|
|
|
|
/* Create the synchronization event.
|
|
|
|
This routine creates a synchronization event using the supplied name and
|
|
returns an event object to the caller. If creation succeeds, the routine
|
|
returns 0. Otherwise, the error value is returned.
|
|
|
|
Systems that define the semaphore functions but implement them as stubs will
|
|
return ENOSYS. We handle this case by enabling fallback to the unimplemented
|
|
behavior, i.e., emulating a process wait by a timed pause and delaying EDT
|
|
to avoid a race condition. Error returns are reported back to the caller in
|
|
either case.
|
|
|
|
Regarding the choice of event name, the Single UNIX Standard says:
|
|
|
|
If [the] name begins with the <slash> character, then processes calling
|
|
sem_open() with the same value of name shall refer to the same semaphore
|
|
object, as long as that name has not been removed. If name does not begin
|
|
with the <slash> character, the effect is implementation-defined.
|
|
|
|
Therefore, event names passed to this routine should begin with a slash
|
|
character.
|
|
|
|
The event is created as initially not-signaled.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. We enable the EDT delay after an IOP-to-SP data transfer completes to
|
|
help ameliorate the race condition that would otherwise occur. See the
|
|
notes in the "ipl_interface" routine for details.
|
|
*/
|
|
|
|
static uint32 create_event (const char *name, EVENT *event)
|
|
{
|
|
*event = sem_open (name, O_CREAT, S_IRWXU, 0); /* create an initially not-signaled event */
|
|
|
|
if (*event == SEM_FAILED) { /* if event creation failed */
|
|
if (errno == ENOSYS) { /* then if the function is not implemented */
|
|
edt_delay = 1; /* then enable the EDT delay workaround */
|
|
|
|
tprintf (iplo_dev, TRACE_STATE, "sem_open is unsupported on this system; using fallback\n");
|
|
}
|
|
|
|
else /* otherwise it is an unexpected error */
|
|
tprintf (iplo_dev, TRACE_STATE, "Creation with identifier \"%s\" failed with error %u\n",
|
|
name, errno);
|
|
|
|
return (uint32) errno; /* return the error code to indicate failure */
|
|
}
|
|
|
|
else { /* otherwise the creation succeeded */
|
|
tprintf (iplo_dev, TRACE_STATE, "Created event %p with identifier \"%s\"\n",
|
|
(void *) *event, name);
|
|
return 0; /* so return success */
|
|
}
|
|
}
|
|
|
|
|
|
/* Destroy the synchronization event.
|
|
|
|
This routine destroys the synchronization event specified by the supplied
|
|
event name. If destruction succeeds, the event object is invalidated, and
|
|
the routine returns 0. Otherwise, the error value is returned.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. If the other simulator instance destroys the event first, our
|
|
"sem_unlink" call will fail with ENOENT. This is an expected error, and
|
|
the routine returns success in this case.
|
|
*/
|
|
|
|
static uint32 destroy_event (const char *name, EVENT *event)
|
|
{
|
|
int status;
|
|
|
|
if (*event != SEM_FAILED) { /* if the event exists */
|
|
status = sem_unlink (name); /* then delete it */
|
|
|
|
if (status != 0 && errno != ENOENT) { /* if the deletion failed */
|
|
tprintf (iplo_dev, TRACE_STATE, "Destruction of event %p failed with error %u\n",
|
|
(void *) *event, errno);
|
|
|
|
return (uint32) errno; /* then return the error code */
|
|
}
|
|
|
|
else /* otherwise the deletion succeeded */
|
|
tprintf (iplo_dev, TRACE_STATE, "Destroyed event %p\n",
|
|
(void *) *event);
|
|
|
|
*event = SEM_FAILED; /* reset the event handle */
|
|
}
|
|
|
|
return 0; /* return success */
|
|
}
|
|
|
|
|
|
/* Wait for the synchronization event.
|
|
|
|
This routine waits for a synchronization event to be signaled or for the
|
|
supplied maximum wait time to elapse. If the event identified by the
|
|
supplied event object is signaled, the routine returns 0 and sets the
|
|
"signaled" flag to TRUE. If the timeout expires without the event being
|
|
signaled, the routine returns 0 with the "signaled" flag set to FALSE. If
|
|
the event wait fails, the routine returns the error value.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The maximum wait time may be zero to test the signaled state and return
|
|
immediately, or may be set to a large value to wait forever. The latter
|
|
is not recommended, as it provides the user with no means to cancel the
|
|
wait and return to the SCP prompt.
|
|
*/
|
|
|
|
static uint32 wait_event (EVENT event, uint32 wait_in_ms, t_bool *signaled)
|
|
{
|
|
const time_t wait_s = (time_t) (wait_in_ms / 1000); /* interval wait time in seconds */
|
|
const long wait_ns = (long) (wait_in_ms % 1000) * 1000000; /* interval wait time in nanoseconds */
|
|
struct timespec until_time;
|
|
int status;
|
|
|
|
if (clock_gettime (CLOCK_REALTIME, &until_time)) { /* get the current time; if it failed */
|
|
tprintf (iplo_dev, TRACE_STATE, "Wait for event %p failed with clock error %u\n",
|
|
(void *) event, errno);
|
|
|
|
return (uint32) errno; /* then return the error number */
|
|
}
|
|
|
|
else { /* otherwise */
|
|
until_time.tv_sec = until_time.tv_sec + wait_s; /* set the (absolute) */
|
|
until_time.tv_nsec = until_time.tv_nsec + wait_ns; /* timeout expiration */
|
|
|
|
if (until_time.tv_nsec >= 1000000000) { /* if the nanosecond count overflowed */
|
|
until_time.tv_nsec -= 1000000000; /* then rescale it */
|
|
until_time.tv_sec += 1; /* to fit */
|
|
}
|
|
}
|
|
|
|
status = sem_timedwait (event, &until_time); /* wait for the event, but not forever */
|
|
|
|
*signaled = (status == 0); /* set the flag TRUE if the wait did not time out */
|
|
|
|
if (status) /* if the wait terminated */
|
|
if (errno == ETIMEDOUT || errno == EINTR) /* then note if it timed out or was manually aborted */
|
|
tprintf (iplo_dev, TRACE_STATE, "Event %p wait timed out\n",
|
|
(void *) event);
|
|
|
|
else { /* otherwise it's an unexpected error */
|
|
tprintf (iplo_dev, TRACE_STATE, "Wait for event %p failed with error %u\n",
|
|
(void *) event, errno);
|
|
|
|
return (uint32) errno; /* so return the error code */
|
|
}
|
|
|
|
else /* otherwise the event is signaled */
|
|
tprintf (iplo_dev, TRACE_STATE, "Event %p wait signaled\n",
|
|
(void *) event);
|
|
|
|
return 0; /* return success */
|
|
}
|
|
|
|
|
|
/* Signal the synchronization event.
|
|
|
|
This routine signals a the synchronization event specified by the supplied
|
|
event handle. If signaling succeeds, the routine returns 0. Otherwise, the
|
|
error value is returned.
|
|
*/
|
|
|
|
static uint32 signal_event (EVENT event)
|
|
{
|
|
int status;
|
|
|
|
status = sem_post (event); /* signal the event */
|
|
|
|
if (status) { /* if the call failed */
|
|
tprintf (iplo_dev, TRACE_STATE, "Signal of event %p failed with error %u\n",
|
|
(void *) event, errno);
|
|
|
|
return (uint32) errno; /* then return the error code */
|
|
}
|
|
|
|
else /* otherwise the event was signaled */
|
|
tprintf (iplo_dev, TRACE_STATE, "Event %p signaled\n",
|
|
(void *) event);
|
|
|
|
return 0; /* return success */
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
/* Process synchronization stubs.
|
|
|
|
The stubs generally return failure to inform the caller that host support
|
|
for the expected behavior is not available. It is up to the caller to supply
|
|
a fallback mechanism, if desired. An exception is the "destroy_event"
|
|
function. This returns success to indicate that the events no longer exist
|
|
(and indeed never existed).
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. We enable the EDT delay after an IOP-to-SP data transfer completes to
|
|
help ameliorate the race condition that would otherwise occur. See the
|
|
notes in the "ipl_interface" routine for details.
|
|
*/
|
|
|
|
static uint32 create_event (const char *name, EVENT *event)
|
|
{
|
|
tprintf (iplo_dev, TRACE_STATE, "Synchronization is unsupported on this system; using fallback\n");
|
|
|
|
edt_delay = 1; /* enable the EDT delay workaround */
|
|
return 1; /* and return failure */
|
|
}
|
|
|
|
|
|
static uint32 destroy_event (const char *name, EVENT *event)
|
|
{
|
|
return 0; /* return success */
|
|
}
|
|
|
|
|
|
static uint32 wait_event (EVENT event, uint32 wait_in_ms, t_bool *signaled)
|
|
{
|
|
return 1; /* return failure */
|
|
}
|
|
|
|
|
|
static uint32 signal_event (EVENT event)
|
|
{
|
|
return 1; /* return failure */
|
|
}
|
|
|
|
|
|
#endif
|