blob: e398b2c141700b6cd50d05c6e3671115c692b9ed [file] [log] [blame] [raw]
/* hp2100_ipl.c: HP 12875A Processor Interconnect simulator
Copyright (c) 2002-2016, Robert M. Supnik
Copyright (c) 2017-2018, 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
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, 12566B-001, 12566B-002, 12566B-003 Microcircuit Interface Kits
Operating and Service Manual
(12566-90015, April 1976)
The 12875A Processor Interconnect Kit consists four 12566A Microcircuit
Interface cards. Two are used in each processor. One card in each system is
used to initiate transmissions to the other, and the second card is used to
receive transmissions from the other. Each pair of cards forms a
bidirectional link, as the sixteen data lines are cross-connected, so that
data sent and status returned are supported. In each processor, data is sent
on the lower priority card and received on the higher priority card. Two
sets of cards are used to support simultaneous transmission in both
directions.
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 "hp2100_defs.h"
#include "hp2100_cpu.h"
#include "sim_timer.h"
#if (SIM_MAJOR >= 4)
#include "sim_fio.h"
#else
#include "sim_shmem.h"
#endif
/* Process synchronization definitions */
/* Windows process synchronization */
#if defined (_WIN32)
#pragma push_macro("CONST")
#undef CONST
#include <windows.h>
#pragma pop_macro("CONST")
typedef HANDLE EVENT; /* the event type */
#define NO_EVENT NULL /* the initial (undefined) event value */
/* UNIX process synchronization */
#elif defined (HAVE_SEMAPHORE)
#include <fcntl.h>
#include <semaphore.h>
#include <time.h>
typedef sem_t *EVENT; /* the event type */
#define NO_EVENT SEM_FAILED /* the initial (undefined) event value */
static t_bool event_fallback = FALSE; /* TRUE if semaphores are defined but not supported */
/* Process synchronization stub */
#else
typedef uint32 EVENT; /* the event type */
#define NO_EVENT 0 /* the initial (undefined) event value */
#endif
/* Program limits */
#define CARD_COUNT 2 /* count of cards supported */
/* 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 ipli_unit ipl_unit [ipli] /* inbound card unit */
#define iplo_unit ipl_unit [iplo] /* outbound card unit */
/* Device information block references */
#define ipli_dib ipl_dib [ipli] /* inbound card DIB */
#define iplo_dib ipl_dib [iplo] /* outbound card DIB */
/* IPL 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 flagbuf; /* 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 IO_STATE IO_ARRAY [CARD_COUNT]; /* an array of I/O states for the two cards */
static IO_ARRAY dev_bus; /* the local device I/O bus states */
typedef struct {
INPUT_STATE_PTR input; /* the input accessor */
OUTPUT_STATE_PTR output; /* the output accessor */
} STATE_PTRS;
static STATE_PTRS io_ptrs [CARD_COUNT] = { /* the card accessors pointing at the local state */
{ &dev_bus [ipli].forward.input, /* card [0].input */
&dev_bus [ipli].forward.output }, /* card [0].output */
{ &dev_bus [iplo].forward.input, /* card [1].input */
&dev_bus [iplo].forward.output } /* card [1].output */
};
/* IPL interface state */
static t_bool cpu_is_iop = FALSE; /* TRUE if this is the IOP instance, FALSE if SP instance */
static int32 edt_delay = 1; /* EDT delay (msec) */
static int32 poll_wait = 50; /* maximum poll wait time */
static char event_name [PATH_MAX]; /* the event name */
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 event_id = NO_EVENT; /* the synchronization event */
static SHMEM *memory_region = NULL; /* a pointer to the shared memory region descriptor */
/* IPL local SCP support routines */
static IOHANDLER ipl_interface;
static t_stat ipl_set_diag (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat ipl_set_sync (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
static t_stat ipl_reset (DEVICE *dptr);
static t_stat ipl_boot (int32 unitno, DEVICE *dptr);
static t_stat ipl_attach (UNIT *uptr, CONST char *cptr);
static t_stat ipl_detach (UNIT *uptr);
/* IPL local utility routines */
static t_stat card_service (UNIT *uptr);
static void activate_unit (UNIT *uptr);
static void abort_handler (int signal);
/* Process synchronization routines */
static uint32 create_event (const char *name, EVENT *event);
static uint32 destroy_event (const char *name, EVENT *event);
static t_bool event_is_undefined (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 if multiple interfaces are supported */
{ &ipl_interface, /* the device's I/O interface function pointer */
IPLO, /* the device's select code (02-77) */
1 } /* the card index if multiple interfaces are supported */
};
/* Unit lists */
static UNIT ipl_unit [CARD_COUNT] = {
{ UDATA (&card_service, UNIT_ATTABLE, 0) },
{ UDATA (&card_service, UNIT_ATTABLE, 0) }
};
/* Register lists */
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].flagbuf, 0) },
{ DRDATA (TIME, poll_wait, 24), PV_LEFT },
{ DRDATA (EDTDELAY, edt_delay, 32), PV_LEFT | REG_HIDDEN },
{ DRDATA (EVTERR, event_error, 32), PV_LEFT | REG_HRO },
{ ORDATA (SC, ipli_dib.select_code, 6), REG_HRO },
{ ORDATA (DEVNO, ipli_dib.select_code, 6), REG_HRO },
{ 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].flagbuf, 0) },
{ DRDATA (TIME, poll_wait, 24), PV_LEFT },
{ ORDATA (SC, iplo_dib.select_code, 6), REG_HRO },
{ ORDATA (DEVNO, iplo_dib.select_code, 6), REG_HRO },
{ NULL }
};
/* Modifier lists */
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, 0u, NULL, "WAIT", &ipl_set_sync, NULL, NULL },
{ MTAB_XDV, 1u, NULL, "SIGNAL", &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 ipl_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 }
};
/* Device descriptors */
DEVICE ipli_dev = {
"IPL", /* device name (logical name "IPLI") */
&ipli_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 */
ipl_deb, /* debug flag name table */
NULL, /* memory size change routine */
NULL /* logical device name */
};
DEVICE iplo_dev = {
"IPLO", /* device name */
&iplo_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 */
ipl_deb, /* debug flag name table */
NULL, /* memory size change routine */
NULL /* logical device name */
};
static DEVICE *dptrs [CARD_COUNT] = {
&ipli_dev,
&iplo_dev
};
/* I/O signal handler for the IPLI and IPLO devices.
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. 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 (2883.asm, 7900.asm,
790X.asm, 79X3.asm, and 79XX.asm):
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
The STC/CLC normally would cause a second "request device table" command
to be recognized by the IOP, except that the IOP DMA setup routine
"DMAXF" (in D61.asm) has specified an end-of-block CLC that holds off the
IPL interrupt, and the completion interrupt routine "DMCMP" ends with a
STC,C that clears the IPL flag.
In hardware, the two CPUs are essentially interlocked by the DMA
transfer, and DMA completion interrupts occur almost simultaneously.
Therefore, the STC/CLC in the SP is guaranteed to occur before the STC,C
in the IOP. Under simulation, and especially on multiprocessor hosts,
that guarantee does not hold. If the STC/CLC occurs after the STC,C,
then the IOP starts a second device table DMA transfer, which the SP is
not expecting. The IOP never processes the subsequent "start
timesharing" command, and the multiplexer is non-responsive.
We employ a workaround that decreases the incidence of the problem: DMA
output completion interrupts are delayed to allow the other SIMH instance
a chance to process its own DMA completion. We do this by processing the
EDT (End Data Transfer) I/O backplane signal and "sleep"ing for a short
time if the transfer was an output transfer to the input channel, i.e.,
a data response to the SP. 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, "edt_delay", is initialized to one millisecond but
is exposed via a hidden IPLI register, "EDTDELAY", that allows the user
to lengthen the delay if necessary.
The condition is only improved, and not solved, because "sleep"ing the
IOP doesn't guarantee that the SP will actually execute. It's possible
that a higher-priority host process will preempt the SP, and that at the
sleep 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 uint32 ipl_interface (DIB *dibptr, IOCYCLE signal_set, uint32 stat_data)
{
const char *iotype [] = { "Status", "Command" };
const CARD_INDEX card = (CARD_INDEX) dibptr->card_index; /* set card selector */
UNIT *const uptr = &(ipl_unit [card]); /* associated unit pointer */
IOSIGNAL signal;
IOCYCLE working_set = IOADDSIR (signal_set); /* add ioSIR if needed */
while (working_set) {
signal = IONEXT (working_set); /* isolate next signal */
switch (signal) { /* dispatch I/O signal */
case ioCLF: /* clear flag flip-flop */
ipl [card].flag = CLEAR;
ipl [card].flagbuf = CLEAR;
break;
case ioSTF: /* set flag flip-flop */
case ioENF: /* enable flag */
ipl [card].flag = SET;
ipl [card].flagbuf = SET;
break;
case ioSFC: /* skip if flag is clear */
setstdSKF (ipl [card]);
break;
case ioSFS: /* skip if flag is set */
setstdSKF (ipl [card]);
break;
case ioIOI: /* I/O data input */
stat_data = IORETURN (SCPE_OK, ipl [card].input_word); /* get return data */
tpprintf (dptrs [card], TRACE_CSRW, "%s input word is %06o\n",
iotype [card ^ 1], ipl [card].input_word);
break;
case ioIOO: /* I/O data output */
ipl [card].output_word = IODATA (stat_data); /* clear supplied status */
io_ptrs [card].output->data_out = ipl [card].output_word;
tpprintf (dptrs [card], TRACE_CSRW, "%s output word is %06o\n",
iotype [card], ipl [card].output_word);
break;
case ioPOPIO: /* power-on preset to I/O */
ipl [card].flag = SET; /* set flag buffer and flag */
ipl [card].flagbuf = SET;
ipl [card].output_word = 0; /* clear output buffer */
io_ptrs [card].output->data_out = 0;
break;
case ioCRS: /* control reset */
case ioCLC: /* clear control flip-flop */
ipl [card].control = CLEAR; /* clear ctl */
break;
case ioSTC: /* set control flip-flop */
ipl [card].control = SET; /* set ctl */
io_ptrs [card].output->device_command_out = TRUE; /* assert Device Command */
if (uptr->flags & UNIT_DIAG) /* if this card is in the diagnostic mode */
if (ipl_unit [card ^ 1].flags & UNIT_DIAG) { /* then if both cards are in diagnostic mode */
ipl_unit [card ^ 1].wait = 1; /* then schedule the other card */
activate_unit (&ipl_unit [card ^ 1]); /* for immediate reception */
}
else { /* otherwise simulate a loopback */
uptr->wait = 1; /* by scheduling this card */
activate_unit (uptr); /* for immediate reception */
}
tpprintf (dptrs [card], TRACE_XFER, "Word %06o sent to link\n",
ipl [card].output_word);
break;
case ioEDT: /* end data transfer */
if (cpu_is_iop /* if this is the IOP instance */
&& signal_set & ioIOO /* and the card is doing output */
&& card == ipli) { /* on the input card */
tprintf (ipli_dev, TRACE_CMD, "Delaying DMA completion interrupt for %d msec\n",
edt_delay);
sim_os_ms_sleep (edt_delay); /* then delay DMA completion */
}
break;
case ioSIR: /* set interrupt request */
setstdPRL (ipl [card]);
setstdIRQ (ipl [card]);
setstdSRQ (ipl [card]);
break;
case ioIAK: /* interrupt acknowledge */
ipl [card].flagbuf = CLEAR;
break;
default: /* all other signals */
break; /* are ignored */
}
working_set = working_set & ~signal; /* remove current signal from set */
}
return stat_data;
}
/* Unit service - poll for input */
static t_stat card_service (UNIT *uptr)
{
static uint32 delta [CARD_COUNT] = { 0, 0 }; /* per-card accumulated time between receptions */
const CARD_INDEX card = (CARD_INDEX) (uptr == &iplo_unit); /* set card selector */
t_stat status = SCPE_OK;
tpprintf (dptrs [card], TRACE_PSERV, "Poll delay %d service entered\n",
uptr->wait);
delta [card] = delta [card] + uptr->wait; /* update the accumulated time */
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 [card]);
ipl_interface (&ipl_dib [card], ioENF, 0); /* set the flag */
io_ptrs [card].output->device_command_out = FALSE; /* reset Device Command */
uptr->wait = 1; /* restart polling at the minimum time */
delta [card] = 0; /* and clear the accumulated time */
}
else { /* otherwise Device Flag is denied */
uptr->wait = uptr-> wait * 2; /* so double the wait time for the next check */
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 [card].input->cable_connected == FALSE /* if the interconnecting cable is not present */
&& cpu_ss_ioerr != SCPE_OK) { /* and the I/O error stop is enabled */
cpu_ioerr_uptr = uptr; /* then save the failing unit */
status = STOP_NOCONN; /* and report the disconnection */
}
}
if (uptr->flags & UNIT_ATT) /* if the link is active */
activate_unit (uptr); /* then continue to poll for input */
return status; /* return the event service status */
}
/* 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.
*/
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;
ipl [card].output_word = 0;
if (ipli_dev.lname == NULL) /* logical name unassigned? */
ipli_dev.lname = strdup ("IPLI"); /* allocate and initialize the name */
}
IOPRESET (dibptr); /* PRESET device (does not use PON) */
if (uptr->flags & UNIT_ATT) { /* if the link is active */
uptr->wait = poll_wait; /* then continue to poll for input */
activate_unit (uptr); /* at the idle rate */
}
else /* otherwise the link is inactive */
sim_cancel (uptr); /* 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 ] 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 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 ] [ IPLI | IPLO] <port-1>
ATTACH -C [ 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 an event (semaphore) 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.
Two object names that identify the shared memory region and synchronization
event are derived from the <code> (or lower <port>) number:
/HP 2100-MEM-<code>
/HP 2100-EVT-<code>
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 creation fails, the routine returns "Command not completed" status to
indicate that interconnection without synchronization is permitted.
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.
*/
static t_stat ipl_attach (UNIT *uptr, CONST char *cptr)
{
t_stat status;
int32 id_number;
char object_name [PATH_MAX];
CONST char *zptr;
char *tptr;
IO_STATE_PTR isp;
UNIT *optr;
if ((ipli_unit.flags | iplo_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 */
uptr->wait = poll_wait; /* set up the initial poll time */
activate_unit (uptr); /* and activate the unit to poll */
}
if ((sim_switches & (SP | IOP)) == 0) /* if this is not a single-device attach */
if (ipli_unit.ID == 0 || iplo_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 (ipli_unit.ID < iplo_unit.ID) /* otherwise */
id_number = ipli_unit.ID; /* determine */
else /* the lower */
id_number = iplo_unit.ID; /* ID number */
else { /* otherwise this is a single-device attach */
if (uptr == &ipli_unit) /* so if we are attaching the input unit */
optr = &iplo_unit; /* then point at the output unit */
else /* otherwise we are attaching the output unit */
optr = &ipli_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 */
optr->wait = poll_wait; /* set up the initial poll time */
activate_unit (optr); /* and activate the unit to poll */
}
sprintf (object_name, "/%s-MEM-%d", /* generate the shared memory area name */
sim_name, id_number);
status = sim_shmem_open (object_name, sizeof dev_bus, /* allocate the shared memory area */
&memory_region, (void **) &isp);
if (status != SCPE_OK) { /* if the allocation failed */
ipl_detach (uptr); /* then detach this unit */
return status; /* and report the error */
}
else { /* otherwise */
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 */
}
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 */
}
io_ptrs [ipli].output->cable_connected = TRUE; /* indicate that the cables */
io_ptrs [iplo].output->cable_connected = TRUE; /* have been connected */
}
sprintf (event_name, "/%s-EVT-%d", /* generate the process synchronization event name */
sim_name, id_number);
event_error = create_event (event_name, &event_id); /* create the event */
if (event_error == 0) /* if event creation succeeded */
return SCPE_OK; /* then report a successful attach */
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 event
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 is
also stopped.
If the event destruction failed, the routine returns "Command not completed"
status to indicate that the error code register should be checked.
*/
static t_stat ipl_detach (UNIT *uptr)
{
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 (ipli_unit.filename == iplo_unit.filename) { /* if both units are attached to the same object */
if (uptr == &ipli_unit) /* then if we are detaching the input unit */
optr = &iplo_unit; /* then point at the output unit */
else /* otherwise we are detaching the output unit */
optr = &ipli_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 */
sim_cancel (optr); /* cancel the other unit's poll */
}
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 (uptr); /* cancel the poll */
io_ptrs [ipli].output->cable_connected = FALSE; /* disconnect the cables */
io_ptrs [iplo].output->cable_connected = FALSE; /* from both cards */
io_ptrs [ipli].input = &dev_bus [ipli].forward.input; /* restore local control */
io_ptrs [ipli].output = &dev_bus [ipli].forward.output; /* over the I/O state */
io_ptrs [iplo].input = &dev_bus [iplo].forward.input; /* for both cards */
io_ptrs [iplo].output = &dev_bus [iplo].forward.output;
sim_shmem_close (memory_region); /* deallocate the shared memory region */
memory_region = NULL; /* and clear the region pointer */
event_error = destroy_event (event_name, &event_id); /* destroy the 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 */
}
/* 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, CONST char *cptr, void *desc)
{
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 (ipli_unit.flags & iplo_unit.flags & UNIT_DIAG) { /* if both devices are now in diagnostic mode */
io_ptrs [ipli].input = &dev_bus [iplo].reverse.input; /* then connect the cable */
io_ptrs [ipli].output = &dev_bus [ipli].forward.output; /* so that the outputs of one card */
io_ptrs [iplo].input = &dev_bus [ipli].reverse.input; /* are connected to the inputs of the other card */
io_ptrs [iplo].output = &dev_bus [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 (ipli_unit.flags & UNIT_DIAG) { /* if the input card is in diagnostic mode */
io_ptrs [ipli].input = &dev_bus [ipli].reverse.input; /* then loop the card outputs */
io_ptrs [ipli].output = &dev_bus [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 = &dev_bus [ipli].forward.input; /* so point at the card state */
io_ptrs [ipli].output = &dev_bus [ipli].forward.output; /* in the normal direction */
io_ptrs [ipli].output->cable_connected = FALSE; /* and indicate that the card is not connected */
}
if (iplo_unit.flags & UNIT_DIAG) { /* otherwise */
io_ptrs [iplo].input = &dev_bus [iplo].reverse.input; /* if the output card is in diagnostic mode */
io_ptrs [iplo].output = &dev_bus [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 = &dev_bus [iplo].forward.input; /* otherwise the card is in link mode */
io_ptrs [iplo].output = &dev_bus [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 "value" parameter set to zero to
wait on the synchronization event or non-zero to signal the synchronization
event. The unit, character, and descriptor pointers are not used.
This routine is called for the following commands:
SET IPLI WAIT
SET IPLI SIGNAL
If the event object has not been created yet by attaching the IPL device, the
routine returns "Command not allowed" status. For either command, the
routine returns "Command not completed" if the signal or wait function
returned an error to indicate that the error code register should be checked.
To permit the user to abort the wait command, a CTRL+C handler is installed,
and waits of one second each are performed in a loop. The handler will set
the "wait_aborted" variable TRUE if it is called, and the loop then will
terminate and return with success status.
Implementation notes:
1. The "wait_event" routine returns TRUE if the event is signaled and FALSE
if it times out while waiting.
*/
static t_stat ipl_set_sync (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
const uint32 wait_time = 1000; /* the wait time in milliseconds */
t_bool signaled;
if (event_is_undefined (event_id)) /* if the event has not been defined yet */
return SCPE_NOFNC; /* then the command is not allowed */
if (value) { /* if this is a SIGNAL command */
event_error = signal_event (event_id); /* then signal the event */
if (event_error == 0) /* if signaling succeeded */
return SCPE_OK; /* then report command success */
else /* otherwise */
return SCPE_INCOMP; /* report that the command did not complete */
}
else { /* otherwise it's a WAIT command */
wait_aborted = FALSE; /* clear the abort flag */
signal (SIGINT, abort_handler); /* and set up the CTRL+C handler */
do
event_error = wait_event (event_id, wait_time, /* wait for the event */
&signaled); /* to be signaled */
while (! (event_error || wait_aborted || signaled)); /* unless an error or user abort occurs */
signal (SIGINT, SIG_DFL); /* restore the default CTRL+C handler */
if (event_error == 0) /* if the wait function succeeded */
return SCPE_OK; /* then report command success */
else /* otherwise */
return SCPE_INCOMP; /* report that the command did not complete */
}
}
/* Handler for the CTRL+C signal.
This handler is installed while waiting for a synchronization event. It is
called if the user presses CTRL+C to abort the wait command.
*/
static void abort_handler (int signal)
{
wait_aborted = TRUE; /* the user has aborted the event wait */
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)
{
const CARD_INDEX card = (CARD_INDEX) (uptr == &iplo_unit); /* set card selector */
tpprintf (dptrs [card], TRACE_PSERV, "Poll delay %u service scheduled\n",
uptr->wait);
sim_activate (uptr, uptr->wait); /* activate the unit with the specified wait */
return;
}
/* 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 */
t_stat status;
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 */
status = 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 (status == SCPE_OK && mem_examine (PR + ptr_scx) <= MAXDEV) { /* if the copy succeeded and is the special BBL */
mem_deposit (PR + ipl_ptx, (HP_WORD) PR + ipl_scx); /* then configure */
mem_deposit (PR + ptr_ptx, (HP_WORD) PR + ptr_scx); /* the pointers */
mem_deposit (PR + ipl_scx, (HP_WORD) ipli_dib.select_code); /* and select codes */
mem_deposit (PR + ptr_scx, (HP_WORD) ptr_sc); /* for the loader */
}
return status; /* return the result of the boot configuration */
}
/* Process synchronization functions */
#if defined (_WIN32)
/* 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)
{
*event = CreateEvent (NULL, FALSE, FALSE, name); /* create an auto-reset, initially not-signaled event */
tprintf (ipli_dev, TRACE_CMD, "Created event %p with identifier \"%s\"\n",
(void *) *event, name);
if (*event == NULL) /* if event creation failed */
return (uint32) GetLastError (); /* then return the error code */
else /* otherwise the creation succeeded */
return 0; /* so return success */
}
/* 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;
if (*event == NULL) /* if the event does not exist */
return 0; /* then indicate that it has already been destroyed */
else { /* otherwise the event exists */
status = CloseHandle (*event); /* so close it */
*event = NULL; /* and clear the event handle */
if (status == FALSE) /* if the close failed */
return (uint32) GetLastError (); /* then return the error code */
else /* otherwise the close succeeded */
return 0; /* so return success */
}
}
/* Test if the synchronization event exists.
This routine returns TRUE if the supplied event handle does not exist and
FALSE if the handle refers to a defined event.
*/
static t_bool event_is_undefined (EVENT event)
{
return (event == NULL); /* return TRUE if the event does not exist */
}
/* 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;
status = WaitForSingleObject (event, wait_time); /* wait for the event, but not forever */
tprintf (ipli_dev, TRACE_CMD, "Wait status is %lu\n", status);
if (status == WAIT_FAILED) /* if the wait failed */
return (uint32) GetLastError (); /* then return the error code */
else { /* otherwise the wait completed */
*signaled = (status != WAIT_TIMEOUT); /* so set the flag TRUE if the wait did not time out */
return 0; /* and 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)
{
BOOL status;
status = SetEvent (event); /* signal the event */
if (status == FALSE) /* if the call failed */
return (uint32) GetLastError (); /* then return the error code */
else /* otherwise the signal succeeded */
return 0; /* so return success */
}
#elif defined (HAVE_SEMAPHORE)
/* 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 two-second sleep. All other
error returns are reported back to the caller.
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.
*/
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 */
tprintf (ipli_dev, TRACE_CMD, "sem_open is unsupported on this system; using fallback\n");
event_fallback = TRUE; /* then fall back to event emulation */
return 0; /* and claim that the open succeeded */
}
else { /* otherwise it is an unexpected error */
tprintf (ipli_dev, TRACE_CMD, "sem_open error is %u\n", errno);
return (uint32) errno; /* so return the error code */
}
else { /* otherwise the creation succeeded */
tprintf (ipli_dev, TRACE_CMD, "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 does not exist */
return 0; /* then indicate that it has already been deleted */
else { /* otherwise the event exists */
status = sem_unlink (name); /* so delete it */
*event = SEM_FAILED; /* and clear the event handle */
if (status != 0 && errno != ENOENT) { /* if the deletion failed */
tprintf (ipli_dev, TRACE_CMD, "sem_unlink error is %u\n", errno);
return (uint32) errno; /* then return the error code */
}
else /* otherwise the deletion succeeded */
return 0; /* so return success */
}
}
/* Test if the synchronization event exists.
This routine returns TRUE if the supplied event object does not exist and
FALSE if the object refers to a defined event.
*/
static t_bool event_is_undefined (EVENT event)
{
if (event_fallback) /* if events are being emulated */
return FALSE; /* then claim that the event is defined */
else /* otherwise */
return (event == SEM_FAILED); /* return TRUE if the event does not exist */
}
/* 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 int wait_time = (int) wait_in_ms / 1000; /* interval wait time in seconds */
struct timespec until_time;
int status;
if (event_fallback) { /* if events are being emulated */
sim_os_sleep (2); /* then wait for two seconds */
*signaled = TRUE; /* indicate a signaled completion */
return 0; /* and return success */
}
else if (clock_gettime (CLOCK_REALTIME, &until_time)) { /* get the current time; if it failed */
tprintf (ipli_dev, TRACE_CMD, "clock_gettime error is %u\n", errno);
return (uint32) errno; /* then return the error number */
}
else /* otherwise */
until_time.tv_sec = until_time.tv_sec + wait_time; /* set the (absolute) timeout expiration */
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 if it timed out or was manually aborted */
return 0; /* then return success */
else { /* otherwise it's an unexpected error */
tprintf (ipli_dev, TRACE_CMD, "sem_timedwait error is %u\n", errno);
return (uint32) errno; /* so return the error code */
}
else /* otherwise the event is signaled */
return 0; /* so 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;
if (event_fallback) /* if events are being emulated */
return 0; /* then claim that the event was signaled */
else /* otherwise */
status = sem_post (event); /* signal the event */
if (status) { /* if the call failed */
tprintf (ipli_dev, TRACE_CMD, "sem_post error is %u\n", errno);
return (uint32) errno; /* then return the error code */
}
else /* otherwise the event was signaled */
return 0; /* so return success */
}
#else
/* Process synchronization stubs.
The stubs return success, rather than failure, because we want the callers to
continue as though synchronization is occurring, even though the host system
does not support the operations necessary to implement this. Without host
system synchronization support, the simulated system's OS might work, but if
the routines returned failure, then the simulator command files running on
such systems would refuse to run.
Implementation notes:
1. We provide an event wait by sleeping for a few seconds to give the other
simulator instance a chance to catch up. This has a reasonable chance of
working, provided the other instance isn't preempted during the sleep.
*/
static uint32 create_event (const char *name, EVENT *event)
{
tprintf (ipli_dev, TRACE_CMD, "Synchronization is unsupported on this system; using fallback\n");
return 0;
}
static uint32 destroy_event (const char *name, EVENT *event)
{
return 0;
}
static t_bool event_is_undefined (EVENT event)
{
return FALSE;
}
static uint32 wait_event (EVENT event, uint32 wait_in_ms, t_bool *signaled)
{
sim_os_sleep (2); /* wait for two seconds */
*signaled = TRUE; /* then indicate a signaled completion */
return 0; /* and return success */
}
static uint32 signal_event (EVENT event)
{
return 0;
}
#endif