- This release of the HP 3000 simulator adds the following device simulation: - 30209A Line Printer Controller with One 2607/13/17/18 Line Printer The simulation supports the use of custom VFU tape images, as well as the built-in HP-standard VFU tape. The simulated device name is "LP". The full set of configurable options is detailed in a new section of the HP 3000 Simulator User's Guide. In addition, the preconfigured MPE-V/R disc image has been updated to add the following features: - The MPE cold load command files attach the line printer to the "lp.txt" output file and specify the "-n" option to clear the file before use. - Preinstalled User-Defined Commands (UDCs) provide access to the COBOL 74 compiler with the MPE-V/E :COBOLII, :COBOLIIPREP, and :COBOLIIGO commands, and to the COBOL 85 compiler with :COBOLIIX, :COBOLIIXPREP, and :COBOLIIXGO. However, see the implementation note below. -------------------- Implementation Notes -------------------- - MPE requires a line printer, so it is recommended that the MPE startup simulator command file include an ATTACH LP <filename> command to load paper into the printer before cold loading. If the printer is not attached, it will appear to MPE to be out of paper. - The line printer terminates each print line with an HP-standard CR/LF pair. If the output file is to be retained as a text file on a Unix system, removal of the carriage returns, e.g., via the "dos2unix" utility, may be desirable. - The simulator currently does not provide the HP 32234A COBOL II firmware instructions, so programs generated by the COBOLII compiler will abort at run time with an "ILLEGAL INSTRUCTION" error. Programs generated by the COBOL compiler do not use these instructions and therefore are not affected. ---------- Bugs Fixed ---------- 1. PROBLEM: The effective address of a byte pointer with a negative index is calculated incorrectly. VERSION: Release 1 OBSERVATION: Defining a :WELCOME message in MPE appears to work, but when the next logon attempts to print the message, an infinite number of CRLFs are printed instead. CAUSE: The welcome message is stored in an extra data segment. The format for each message line is a line length stored in the lower byte of the word preceding the message string. The code defines BYTE POINTER NEXTLINE and points NEXTLINE to the first message character. The line length is set with NEXTLINE(-1) := IOCOUNT. This generates a LOAD <IOCOUNT> ; LDXN 1 ; STB <NEXTLINE>,I,X sequence. In the "cpu_ea" routine, the indexing adds the X register value (-1) to the byte pointer (NEXTLINE). This causes an overflow that is not masked to 16 bits. For a word access, this displacement is added to the base register and then masked to 16 bits, which gives the correct value. However, for byte accesses, the displacement is divided by 2 and then added, and the sum is masked. Dividing by 2 shifts the overflow bit into the MSB, causing the addition result to be off by 32K. The STB goes to the wrong location, the original zero in the length byte location is retained, and when the welcome message is printed, a zero-length line is printed, and the byte pointer is incremented by zero, so the null line is printed forever. RESOLUTION: Modify "cpu_ea" (hp3000_cpu.c) to mask indexed displacements to 16 bits after adding the X register value. STATUS: Fixed in Release 2. 2. PROBLEM: An SMSK instruction may clear the interrupt mask flip-flop of a device that specifies that it is should be "always enabled." VERSION: Release 1 OBSERVATION: If the TOS word is zero, an SMSK instruction will clear the interrupt mask flip-flop of a device whose mask jumper is set to "E" (always enabled). CAUSE: In response to a DSETMASK signal, device interfaces set their interrupt mask flip-flops by "anding" the incoming data word with the interrupt mask jumper setting. The jumper setting value for "always enabled" is %177777, which sets the mask flip-flop in all cases, except when the data word is zero. RESOLUTION: Modify hp3000_atc.c, hp3000_ds.c, and hp3000_ms.c to set their mask flip-flops unconditionally if the jumper setting is "E". STATUS: Fixed in Release 2. 3. PROBLEM: The "SET <dev> INTMASK=<n>" command sets the wrong bit in the device interface's interrupt mask jumper setting. VERSION: Release 1 OBSERVATION: The interrupt mask jumper on a device interface is set by specifying the mask bit number in a "SET <dev> INTMASK=<n>" command. This sets a bit in the device's interrupt mask jumper word corresponding to the bit number requested. However, the bit numbering is incorrect; setting the jumper for bit 15, for example, sets bit 0 of the jumper word. Therefore, the interface's mask flip-flop is not set as expected when an SMSK instruction is executed. CAUSE: The bit numbers were counted from the wrong end of the word. RESOLUTION: Modify "hp_set_dib" and "hp_show_dib" (hp3000_sys.c) to number the bits from the MSB instead of the LSB. STATUS: Fixed in Release 2. 4. PROBLEM: The Multiplexer Channel is not generating the ACKSR signal correctly. VERSION: Release 1 OBSERVATION: The line printer controller hangs when an SIO chained write is performed. The first programmed write completes normally, but the second does not start. The channel is waiting for a service request that does not occur. CAUSE: The service request from the last write of the first block transfer is being cleared by an ACKSR generated by the Multiplexer Channel when it performs the IOCW fetch in State A for the second write request. The channel should omit this ACKSR when the previous I/O order was a chained read or write. However, the simulator is testing the order just fetched (Write) instead of the order that has just completed (Write Chained). RESOLUTION: Modify "mpx_service" (hp3000_mpx.c) to test the correct I/O order in State A. STATUS: Fixed in Release 2.
851 lines
43 KiB
C
851 lines
43 KiB
C
/* hp3000_iop.c: HP 3000 30003B I/O Processor simulator
|
|
|
|
Copyright (c) 2016, 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
|
|
AUTHOR 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 name of the author 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 author.
|
|
|
|
IOP HP 3000 Series III I/O Processor
|
|
|
|
30-Jun-16 JDB Changed REG type of filter array to BRDATA
|
|
08-Jun-16 JDB Corrected %d format to %u for unsigned values
|
|
13-May-16 JDB Modified for revised SCP API function parameter types
|
|
28-Aug-15 JDB First release version
|
|
11-Dec-12 JDB Created
|
|
|
|
References:
|
|
- HP 3000 Series II/III System Reference Manual
|
|
(30000-90020, July 1978)
|
|
- HP 3000 Series III Engineering Diagrams Set
|
|
(30000-90141, April 1980)
|
|
|
|
|
|
The HP 30003B I/O Processor is an integral part of the HP 3000 system. It
|
|
works in conjunction with the CPU and Multiplexer Channel to service the
|
|
device interfaces. All I/O interfaces are connected to the IOP bus, which
|
|
transfers programmed I/O orders to the interfaces and handles memory reads
|
|
and writes between the interfaces and the CPU stack. In addition, it
|
|
provides the memory interface for multiplexer channel transfers and fetches
|
|
I/O program orders from main memory for the channel.
|
|
|
|
Interrupt requests are serviced by the IOP, which asserts an external
|
|
interrupt signal to the CPU. Device controllers request interrupts via the
|
|
IOP, which prioritizes the requests and grants service to the
|
|
highest-priority interrupt. While that interrupt is active, lower-priority
|
|
requests are held off until it becomes inactive, whereupon the next
|
|
highest-priority request is granted. The device number of the interrupting
|
|
device is stored in the IOP's address register; this is used by the CPU
|
|
microcode to access the proper entry in the Device Reference Table, which
|
|
contains the starting address of the I/O handler.
|
|
|
|
In hardware, a device requests an interrupt by asserting INTREQ to the IOP.
|
|
In response, the IOP polls the interfaces by asserting INTPOLLIN to determine
|
|
the highest-priority request. The INTPOLLIN and INTPOLLOUT signals are
|
|
daisy-chained between interfaces, with the position of the interface in the
|
|
chain establishing its priority. Interfaces that are not requesting or
|
|
processing interrupts pass INTPOLLIN to INTPOLLOUT. The first interface in
|
|
the chain that has an interrupt request pending will inhibit INTPOLLOUT and
|
|
will set its Interrupt Active flip-flop. As long as the interrupt is active,
|
|
an interface will break the poll chain by denying INTPOLLOUT. This holds off
|
|
requests from lower-priority devices.
|
|
|
|
To avoid scanning each interface's DIB for interrupt requests, the IOP
|
|
simulator maintains two 32-bit vectors: a global "iop_interrupt_request_set"
|
|
and a local "interrupt_poll_set". Each bit is associated with an interrupt
|
|
priority number from 0-31. The bits of the request set indicate which
|
|
interfaces are requesting interrupts, and the bits of the poll set indicate
|
|
which interfaces will break the poll chain when they are polled. The lowest
|
|
set bit in each indicates the highest-priority interrupting device and the
|
|
highest-priority device handler currently executing, respectively. An
|
|
interface requests an interrupt by asserting INTREQ to the IOP. The IOP then
|
|
sets the request and poll bits corresponding to that interface's interrupt
|
|
priority number. The CPU checks the request set periodically to determine if
|
|
an external interrupt is present.
|
|
|
|
A device's DIB (Device Information Block) contains three values that pertain
|
|
to interrupts: the "interrupt_priority" value determines which bit is set or
|
|
cleared in the bit vectors, the "interrupt_request" flip-flop indicates that
|
|
the interface is requesting an interrupt from the IOP, and the
|
|
"interrupt_active" flip-flop indicates that the device's interrupt handler is
|
|
executing. The two flip-flop values indicate one of four possible interrupt
|
|
states that are reflected in the associated bit of the bit vectors:
|
|
|
|
Interrupt Interrupt Request Poll
|
|
Request Active Set Set Interrupt State
|
|
--------- --------- ------- ---- ------------------------------------
|
|
CLEAR CLEAR 0 0 Not interrupting
|
|
SET CLEAR 1 1 Interrupt requested
|
|
CLEAR SET 0 1 Interrupt acknowledged
|
|
SET SET 1 1 Interrupt requested while in handler
|
|
|
|
The "requested" state corresponds to the device interface asserting the
|
|
INTREQ signal to the IOP. The "acknowledged" state corresponds to the IOP
|
|
conducting a poll via INTPOLL IN and INTPOLL OUT, and the device interface
|
|
responding by inhibiting INTPOLL OUT and asserting INTACK to the IOP.
|
|
|
|
If both the request and active flip-flops are set, the device has requested a
|
|
second interrupt while the first is still being processed. The ATC TDI, for
|
|
example, does this when a CIO is issued to acknowledge an interrupt before
|
|
the IXIT sends an ioRIN to the interface to reset the active flip-flop.
|
|
|
|
Device interfaces maintain the states of their interrupt flip-flops for the
|
|
benefit of IOP initialization. During the instruction execution prelude, the
|
|
IOP will reconstruct its bit vectors from the DIB values. Thereafter, the
|
|
interfaces change their interrupt states in response to signals, and the IOP
|
|
adjusts the bit vectors as needed. The only direct interaction needed is an
|
|
"iop_assert_INTREQ" call from the device interface when an interrupt is
|
|
initially requested.
|
|
|
|
The IOP does not have a programmable interface. It is manipulated directly
|
|
by the CPU microcode to issue direct I/O commands to the device interfaces,
|
|
and by the multiplexer channel to transfer data and I/O programs to and from
|
|
memory.
|
|
|
|
Direct I/O instructions are sent via the IOP Bus to all device interfaces.
|
|
When executing I/O instruc tions, the CPU microcode writes a 16-bit command
|
|
word to the IOP, which then places bits 5-7 of that word onto the IOP Bus as
|
|
IOCMD0-2 as follows:
|
|
|
|
CPU Command IOCMD Generated Action
|
|
Instruction Word 0 1 2 Signal Performed
|
|
----------- ------- ----- --------- --------------------------------
|
|
SIN 100000 0 0 0 DSETINT Set interrupt request flip-flop
|
|
CIO 100400 0 0 1 DCONTSTB Write a control word
|
|
SIO 101000 0 1 0 DSTARTIO Start a channel program
|
|
WIO 101400 0 1 1 DWRITESTB Write a data word
|
|
IXIT 102000 1 0 0 DRESETINT Reset interrupt active flip-flop
|
|
TIO 102400 1 0 1 DSTATSTB Read a status word
|
|
SMSK 103000 1 1 0 DSETMASK Set the interrupt mask flip-flop
|
|
RIO 103400 1 1 1 DREADSTB Read a data word
|
|
|
|
The SIO instruction sends an SIO IOCMD to the device interface via the IOP
|
|
to begin execution of a channel program. The program consists of two-word
|
|
programmed I/O orders, with each pair consisting of an I/O Control Word and
|
|
an I/O Address Word. They are encoded as follows:
|
|
|
|
IOCW IOCW IOAW Generated Action
|
|
0 1 2 3 4-15 0-15 Signal Performed
|
|
------- -------------- -------------- ----------- ---------------------
|
|
0 0 0 0 0 XXXXXXXXXXX Jump Address -- Unconditional Jump
|
|
0 0 0 0 1 XXXXXXXXXXX Jump Address SETJMP Conditional Jump
|
|
0 0 0 1 0 XXXXXXXXXXX Residue Count -- Return Residue
|
|
0 0 0 1 1 XXXXXXXXXXX Bank Address -- Set Bank
|
|
0 0 1 0 X XXXXXXXXXXX --- XXXXXX --- SETINT Interrupt
|
|
0 0 1 1 0 XXXXXXXXXXX Status Value TOGGLESIOOK End without Interrupt
|
|
0 0 1 1 1 XXXXXXXXXXX Status Value SETINT End with Interrupt
|
|
0 1 0 0 Control Word 1 Control Word 2 PCONTSTB Control
|
|
0 1 0 1 X XXXXXXXXXXX Status Value PSTATSTB Sense
|
|
C 1 1 0 Neg Word Count Write Address PWRITESTB Write
|
|
C 1 1 1 Neg Word Count Read Address PREADSTB Read
|
|
|
|
The "Unconditional Jump," "Return Residue," and "Set Bank" orders are
|
|
executed entirely by the channel and are not sent to the device interface.
|
|
|
|
The IOP simulator provides the capability to trace direct I/O commands and
|
|
interrupt requests, as well as memory accesses made on behalf of the
|
|
multiplexer channel. Devices that periodically interrupt, such as the system
|
|
clock, may generate a large number of trace events. To accommodate this, a
|
|
filter may be applied to remove trace events from devices that are not of
|
|
interest.
|
|
*/
|
|
|
|
|
|
#include "hp3000_defs.h"
|
|
#include "hp3000_cpu.h"
|
|
#include "hp3000_cpu_ims.h"
|
|
#include "hp3000_io.h"
|
|
|
|
|
|
|
|
/* Program constants */
|
|
|
|
#define TRACE_ALL D32_UMAX /* enable tracing of all devices */
|
|
|
|
|
|
/* Debug flags.
|
|
|
|
The FILTER macro tests if the supplied device number is to be filtered out of
|
|
the trace stream. It returns the bit in the filter array corresponding to
|
|
the device number. If the bit is set, the trace will be generated;
|
|
otherwise, it will be suppressed.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. Bit 0 is reserved for the memory data trace flag.
|
|
*/
|
|
|
|
#define DEB_DIO (1u << 1) /* trace direct I/O commands */
|
|
#define DEB_IRQ (1u << 2) /* trace interrupt requests */
|
|
|
|
#define FILTER(d) (1u << (d) % 32 & filter [(d) / 32])
|
|
|
|
|
|
/* IOP global data structures */
|
|
|
|
const SIO_ORDER to_sio_order [] = { /* translation of IOCW bits 0-4 to SIO_ORDER */
|
|
sioJUMP, /* 00000 = Jump unconditionally */
|
|
sioJUMPC, /* 00001 = Jump conditionally */
|
|
sioRTRES, /* 00010 = Return residue */
|
|
sioSBANK, /* 00011 = Set bank */
|
|
sioINTRP, /* 00100 = Interrupt */
|
|
sioINTRP, /* 00101 = Interrupt */
|
|
sioEND, /* 00110 = End */
|
|
sioENDIN, /* 00111 = End with interrupt */
|
|
sioCNTL, /* 01000 = Control */
|
|
sioCNTL, /* 01001 = Control */
|
|
sioSENSE, /* 01010 = Sense */
|
|
sioSENSE, /* 01011 = Sense */
|
|
sioWRITE, /* 01100 = Write */
|
|
sioWRITE, /* 01101 = Write */
|
|
sioREAD, /* 01110 = Read */
|
|
sioREAD, /* 01111 = Read */
|
|
sioJUMP, /* 10000 = Jump unconditionally */
|
|
sioJUMPC, /* 10001 = Jump conditionally */
|
|
sioRTRES, /* 10010 = Return residue */
|
|
sioSBANK, /* 10011 = Set bank */
|
|
sioINTRP, /* 10100 = Interrupt */
|
|
sioINTRP, /* 10101 = Interrupt */
|
|
sioEND, /* 10110 = End */
|
|
sioENDIN, /* 10111 = End with interrupt */
|
|
sioCNTL, /* 11000 = Control */
|
|
sioCNTL, /* 11001 = Control */
|
|
sioSENSE, /* 11010 = Sense */
|
|
sioSENSE, /* 11011 = Sense */
|
|
sioWRITEC, /* 11100 = Write Chained */
|
|
sioWRITEC, /* 11101 = Write Chained */
|
|
sioREADC, /* 11110 = Read Chained */
|
|
sioREADC /* 11111 = Read Chained */
|
|
};
|
|
|
|
|
|
const char *const sio_order_name [] = { /* indexed by SIO_ORDER */
|
|
"Jump", /* sioJUMP */
|
|
"Conditional Jump", /* sioJUMPC */
|
|
"Return Residue", /* sioRTRES */
|
|
"Set Bank", /* sioSBANK */
|
|
"Interrupt", /* sioINTRP */
|
|
"End", /* sioEND */
|
|
"End with Interrupt", /* sioENDIN */
|
|
"Control", /* sioCNTL */
|
|
"Sense", /* sioSENSE */
|
|
"Write", /* sioWRITE */
|
|
"Write Chained", /* sioWRITEC */
|
|
"Read", /* sioREAD */
|
|
"Read Chained" /* sioREADC */
|
|
};
|
|
|
|
|
|
/* Global IOP state */
|
|
|
|
uint32 iop_interrupt_request_set = 0; /* the set of interfaces requesting interrupts */
|
|
|
|
|
|
/* Local IOP state */
|
|
|
|
static uint32 IOA = 0; /* I/O Address Register */
|
|
|
|
static uint32 interrupt_poll_set = 0; /* the set of interfaces breaking the poll chain */
|
|
static DIB *devs [DEVNO_MAX + 1]; /* index by device number for I/O instruction dispatch */
|
|
static DIB *irqs [INTPRI_MAX + 1]; /* index by interrupt priority number for interrupt requests */
|
|
|
|
static uint32 filter [4] = { /* filter bitmap for device numbers 0-127 */
|
|
TRACE_ALL,
|
|
TRACE_ALL,
|
|
TRACE_ALL,
|
|
TRACE_ALL
|
|
};
|
|
|
|
|
|
/* IOP local SCP support routines */
|
|
|
|
static t_stat iop_set_filter (UNIT *uptr, int32 value, CONST char *cptr, void *desc);
|
|
static t_stat iop_show_filter (FILE *st, UNIT *uptr, int32 value, CONST void *desc);
|
|
|
|
|
|
/* IOP SCP data structures */
|
|
|
|
|
|
/* Unit list */
|
|
|
|
static UNIT iop_unit [] = { /* a dummy unit to satisfy SCP requirements */
|
|
{ UDATA (NULL, 0, 0) }
|
|
};
|
|
|
|
/* Register list.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The "interrupt_poll_set", "devs", and "irqs" variables need not be SAVEd
|
|
or RESTOREd, as they are rebuilt during the instruction execution
|
|
prelude.
|
|
*/
|
|
|
|
static REG iop_reg [] = {
|
|
/* Macro Name Location Radix Width Depth Flags */
|
|
/* ------ ------ -------- ----- ----- ----- ------- */
|
|
{ ORDATA (IOA, IOA, 8), REG_RO },
|
|
{ BRDATA (FILTER, filter, 2, 32, 4), REG_HRO },
|
|
{ NULL }
|
|
};
|
|
|
|
/* Modifier list */
|
|
|
|
static MTAB iop_mod [] = {
|
|
/* Entry Flags Value Print String Match String Validation Display Descriptor */
|
|
/* ------------------- ----- ------------ ------------ --------------- ---------------- ---------- */
|
|
{ MTAB_XDV | MTAB_NMO, 1, "FILTER", "FILTER", &iop_set_filter, &iop_show_filter, NULL },
|
|
{ MTAB_XDV | MTAB_NMO, 0, "", "NOFILTER", &iop_set_filter, NULL, NULL },
|
|
{ 0 }
|
|
};
|
|
|
|
/* Debugging trace list */
|
|
|
|
static DEBTAB iop_deb [] = {
|
|
{ "DIO", DEB_DIO }, /* direct I/O commands issued */
|
|
{ "IRQ", DEB_IRQ }, /* interrupt requests received */
|
|
{ "DATA", DEB_MDATA }, /* I/O data accesses to memory */
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
/* Device descriptor */
|
|
|
|
DEVICE iop_dev = {
|
|
"IOP", /* device name */
|
|
iop_unit, /* unit array */
|
|
iop_reg, /* register array */
|
|
iop_mod, /* modifier array */
|
|
1, /* number of units */
|
|
8, /* address radix */
|
|
PA_WIDTH, /* address width */
|
|
1, /* address increment */
|
|
8, /* data radix */
|
|
DV_WIDTH, /* data width */
|
|
NULL, /* examine routine */
|
|
NULL, /* deposit routine */
|
|
NULL, /* reset routine */
|
|
NULL, /* boot routine */
|
|
NULL, /* attach routine */
|
|
NULL, /* detach routine */
|
|
NULL, /* device information block pointer */
|
|
DEV_DEBUG, /* device flags */
|
|
0, /* debug control flags */
|
|
iop_deb, /* debug flag name array */
|
|
NULL, /* memory size change routine */
|
|
NULL /* logical device name */
|
|
};
|
|
|
|
|
|
|
|
/* IOP global routines */
|
|
|
|
|
|
|
|
/* Initialize the I/O processor.
|
|
|
|
This routine is called in the instruction prelude to set up the IOP data
|
|
structures prior to beginning execution. It sets up two tables of DIB
|
|
pointers -- one indexed by device number, and a second indexed by interrupt
|
|
request number. This allows fast access to the device interface routine by
|
|
the direct I/O instruction and interrupt poll processors, respectively.
|
|
|
|
It also sets the interrupt request and poll bit vectors from the interrupt
|
|
flip-flop values in the device DIBs and clears the external interrupt flag if
|
|
there are no devices with active interrupts (as the user may have set the
|
|
flag or reset the interrupting device during a simulation stop).
|
|
*/
|
|
|
|
uint32 iop_initialize (void)
|
|
{
|
|
const DEVICE *dptr;
|
|
DIB *dibptr;
|
|
uint32 i, irq;
|
|
|
|
iop_interrupt_request_set = 0; /* set all interrupt requests inactive */
|
|
interrupt_poll_set = 0; /* set all poll continuity bits inactive */
|
|
|
|
memset (devs, 0, sizeof devs); /* clear the device number table */
|
|
memset (irqs, 0, sizeof irqs); /* and the interrupt request table */
|
|
|
|
for (i = 0; sim_devices [i] != NULL; i++) { /* loop through all of the devices */
|
|
dptr = sim_devices [i]; /* get a pointer to the device */
|
|
dibptr = (DIB *) dptr->ctxt; /* and to that device's DIB */
|
|
|
|
if (dibptr && !(dptr->flags & DEV_DIS)) { /* if the DIB exists and the device is enabled */
|
|
if (dibptr->device_number != DEVNO_UNUSED) /* then if the device number is valid */
|
|
devs [dibptr->device_number] = dibptr; /* then set the DIB pointer into the device dispatch table */
|
|
|
|
if (dibptr->interrupt_priority != INTPRI_UNUSED) { /* if the interrupt priority is valid */
|
|
irqs [dibptr->interrupt_priority] = dibptr; /* then set the pointer into the interrupt dispatch table */
|
|
|
|
irq = 1 << dibptr->interrupt_priority; /* get the associated interrupt request bit */
|
|
|
|
if (dibptr->interrupt_request) { /* if the interface is requesting an interrupt */
|
|
iop_interrupt_request_set |= irq; /* then set the request bit */
|
|
interrupt_poll_set |= irq; /* and the poll bit */
|
|
}
|
|
|
|
else if (dibptr->interrupt_active) /* otherwise if the interface has acknowledged an interrupt */
|
|
interrupt_poll_set |= irq; /* then just set the poll bit */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (interrupt_poll_set == 0 || IOA == 0) /* if no device has an active interrupt in progress */
|
|
CPX1 &= ~cpx1_EXTINTR; /* then clear the interrupt flag */
|
|
|
|
return IOA;
|
|
}
|
|
|
|
|
|
/* Poll the interfaces for an active interrupt request.
|
|
|
|
This routine is called in the instruction loop when the request set indicates
|
|
that one or more interrupt requests are pending. It polls the interface
|
|
asserting the highest-priority request. If the interface acknowledges the
|
|
interrupt, the routine sets the "external interrupt" bit in the CPU's CPX1
|
|
register to initiate interrupt processing, sets the IOP's IOA register to the
|
|
the device number of the interrupting device, and returns that value to the
|
|
caller.
|
|
|
|
In hardware, an interface requesting an interrupt with its Interrupt Mask
|
|
flip-flop set will assert a common INTREQ to the IOP. In response, the IOP
|
|
polls the interfaces to determine the highest-priority request by asserting
|
|
INTPOLLIN. The INTPOLLIN and INTPOLLOUT signals are daisy-chained between
|
|
interfaces, with the position of the interface in the chain establishing its
|
|
priority. Interfaces that are not requesting interrupts pass INTPOLLIN to
|
|
INTPOLLOUT. The first interface in the chain that has its Interrupt Request
|
|
flip-flop set will inhibit INTPOLLOUT, set its Interrupt Active flip-flop,
|
|
and assert INTACK to the IOP.
|
|
|
|
To avoid polling interfaces in simulation, an interface will set the
|
|
Interrupt Request flip-flop in its DIB and then call iop_assert_INTREQ. That
|
|
routine sets the request set and poll set bits corresponding to the interrupt
|
|
priority value in the DIB.
|
|
|
|
In the instruction execution loop, if external interrupts are enabled (i.e.,
|
|
the I bit in the status word is set), and iop_interrupt_request_set has one
|
|
or more bits set, this routine is called to select the interrupt request to
|
|
service.
|
|
|
|
The end of priority chain is marked by the highest-priority (lowest-order)
|
|
poll bit that is set. When a poll is performed, a priority mask is generated
|
|
that contains just the highest-priority bit. The device corresponding to
|
|
that bit will then be the recipient of the current interrupt acknowledgement
|
|
cycle. After the interrupt request has been cleared, the poll bit will
|
|
prevent lower-priority interrupts from being serviced.
|
|
|
|
For example:
|
|
|
|
poll set : ...0 0 1 0 0 1 0 0 0 0 0 0 (poll denied at INTPRI 6 and 9)
|
|
priority mask : ...0 0 0 0 0 1 0 0 0 0 0 0 (poll stops at INTPRI 6)
|
|
|
|
The request is then ANDed with the priority mask to determine if a request is
|
|
to be granted:
|
|
|
|
pri mask : ...0 0 0 0 0 1 0 0 0 0 0 0 (allowed interrupt source)
|
|
request set : ...0 0 1 0 0 1 0 0 0 0 0 0 (devices requesting interrupts)
|
|
ANDed value : ...0 0 0 0 0 1 0 0 0 0 0 0 (request to grant = INTPRI 6)
|
|
|
|
Once the interrupt request has been cleared, the poll state is:
|
|
|
|
poll set : ...0 0 1 0 0 1 0 0 0 0 0 0 (poll denied at INTPRI 6 and 9)
|
|
priority mask : ...0 0 0 0 0 1 0 0 0 0 0 0 (poll stops at INTPRI 6)
|
|
request set : ...0 0 1 0 0 0 0 0 0 0 0 0 (devices requesting interrupts)
|
|
ANDed value : ...0 0 0 0 0 0 0 0 0 0 0 0 (request to grant = none)
|
|
|
|
INTPRI 9 will continue to be held off until INTPRI 6 completes its interrupt
|
|
handler and resets the Interrupt Active flip-flop on its interface, which
|
|
also clears the associated poll set bit. At the next poll, INTPRI 9 will be
|
|
granted.
|
|
|
|
This routine determines the request to grant, converts that back to an
|
|
interrupt priority number, and uses that to index into the table of DIBs.
|
|
The interface routine associated with the DIB is called with INTPOLLIN
|
|
asserted.
|
|
|
|
If the interface still has its Interrupt Request flip-flop set, it
|
|
will assert INTACK and return the device number. In response, the IOP will
|
|
save the device number in IOA, set the external interrupt bit in CPX1, and
|
|
return the device number to the CPU. This will cause the CPU to service the
|
|
interrupt.
|
|
|
|
However, if some condition has occurred between the time of the original
|
|
request and this poll, the interface will assert INTPOLLOUT. In response,
|
|
the IOP will clear IOA and the associated bit in the poll set to cancel the
|
|
request.
|
|
|
|
In either case, the associated bit in the request set is cleared.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. The hardware inhibits the interrupt poll if the EXTINT flip-flop is set.
|
|
This prevents a second interrupt from changing IOA until the microcode
|
|
signals its readiness by clearing EXTINT. In simulation, entry with
|
|
cpx1_EXTINTR set returns IOA in lieu of conducting a poll.
|
|
*/
|
|
|
|
uint32 iop_poll (void)
|
|
{
|
|
DIB *dibptr;
|
|
SIGNALS_DATA outbound;
|
|
uint32 ipn, priority_mask, request_granted;
|
|
|
|
if (CPX1 & cpx1_EXTINTR) /* if an external interrupt has been requested */
|
|
return IOA; /* then return the device number in lieu of polling */
|
|
|
|
priority_mask = IOPRIORITY (interrupt_poll_set); /* calculate the priority mask */
|
|
request_granted = priority_mask & iop_interrupt_request_set; /* and determine the request to grant */
|
|
|
|
if (request_granted == 0) /* if no request has been granted */
|
|
return 0; /* then return */
|
|
|
|
for (ipn = 0; !(request_granted & 1); ipn++) /* determine the interrupt priority number */
|
|
request_granted = request_granted >> 1; /* by counting the bits until the set bit is reached */
|
|
|
|
dibptr = irqs [ipn]; /* get the DIB pointer for the request */
|
|
|
|
outbound = dibptr->io_interface (dibptr, INTPOLLIN, 0); /* poll the interface that requested the interrupt */
|
|
|
|
if (outbound & INTACK) { /* if the interface acknowledged the interrupt */
|
|
IOA = IODATA (outbound); /* then save the returned device number */
|
|
CPX1 |= cpx1_EXTINTR; /* and tell the CPU */
|
|
|
|
dprintf (iop_dev, FILTER (dibptr->device_number) ? DEB_IRQ : 0,
|
|
"Device number %u acknowledged interrupt request at priority %u\n",
|
|
dibptr->device_number, ipn);
|
|
}
|
|
|
|
else if (outbound & INTPOLLOUT) { /* otherwise if the interface cancelled the request */
|
|
IOA = 0; /* then clear the device number */
|
|
interrupt_poll_set &= ~priority_mask; /* and the associated bit in the poll set */
|
|
|
|
dprintf (iop_dev, FILTER (dibptr->device_number) ? DEB_IRQ : 0,
|
|
"Device number %u canceled interrupt request at priority %u\n",
|
|
dibptr->device_number, ipn);
|
|
}
|
|
|
|
iop_interrupt_request_set &= ~priority_mask; /* clear the request */
|
|
|
|
return IOA; /* return the interrupting device number */
|
|
}
|
|
|
|
|
|
/* Dispatch an I/O command to an interface.
|
|
|
|
This routine is called by the CPU when executing direct I/O instructions
|
|
to send I/O orders to the indicated device interface. It translates the
|
|
"io_cmd" value to the appropriate I/O signal and calls the signal handler of
|
|
the device interface indicated by the "device_number" with the supplied
|
|
"write_value". The handler return value, if any, is returned as the function
|
|
value. If the supplied device number does not correspond to an enabled
|
|
device, the I/O Timeout bit in CPX1 is set.
|
|
|
|
A "Set Interrupt Mask" order is sent to all active interfaces; the supplied
|
|
device number is ignored. If there are none, the I/O Timeout bit is set.
|
|
All of the other orders are sent only to the specified device. A "Reset
|
|
Interrupt" order clears the corresponding bit from the poll set, unless there
|
|
is a request pending on the device (which may occur if a second interrupt was
|
|
requested while the first was still being processed).
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. For a "Set Interrupt Mask" order, it would be faster to cycle through the
|
|
sim_devices array to find the active devices. However, we use the devs
|
|
array so that interfaces are accessed in DEVNO order, which makes traces
|
|
easier to follow. This is an acceptable tradeoff, as the SMSK
|
|
instruction is used infrequently.
|
|
*/
|
|
|
|
HP_WORD iop_direct_io (HP_WORD device_number, IO_COMMAND io_cmd, HP_WORD write_value)
|
|
{
|
|
static const INBOUND_SIGNAL cmd_to_signal [] = { /* indexed by IO_COMMAND */
|
|
DSETINT, /* ioSIN = set interrupt */
|
|
DCONTSTB, /* ioCIO = control I/O */
|
|
DSTARTIO, /* ioSIO = start I/O */
|
|
DWRITESTB, /* ioWIO = write I/O */
|
|
DRESETINT, /* ioRIN = reset interrupt */
|
|
DSTATSTB, /* ioTIO = test I/O */
|
|
DSETMASK, /* ioSMSK = set interrupt mask */
|
|
DREADSTB /* ioRIO = read I/O */
|
|
};
|
|
|
|
static const char *const io_command_name [] = { /* indexed by IO_COMMAND */
|
|
"Set Interrupt", /* ioSIN */
|
|
"Control I/O", /* ioCIO */
|
|
"Start I/O", /* ioSIO */
|
|
"Write I/O", /* ioWIO */
|
|
"Reset Interrupt", /* ioRIN */
|
|
"Test I/O", /* ioTIO */
|
|
"Set Interrupt Mask", /* ioSMSK */
|
|
"Read I/O" /* ioRIO */
|
|
};
|
|
|
|
uint32 irq, devno;
|
|
t_bool no_response;
|
|
DIB *dibptr;
|
|
SIGNALS_DATA outbound = NO_SIGNALS;
|
|
|
|
if (io_cmd == ioSMSK) { /* if the I/O order is "Set Interrupt Mask" */
|
|
no_response = TRUE; /* then check for responding devices */
|
|
|
|
for (devno = 0; devno <= DEVNO_MAX; devno++) { /* loop through the device number list */
|
|
dibptr = devs [devno]; /* to get a device information block pointer */
|
|
|
|
if (dibptr /* if this device is defined */
|
|
&& dibptr->interrupt_mask != INTMASK_UNUSED) { /* and uses the interrupt mask */
|
|
|
|
dprintf (iop_dev, FILTER (devno) ? DEB_DIO : 0,
|
|
"%s order sent to device number %u\n",
|
|
io_command_name [io_cmd], devno);
|
|
|
|
outbound =
|
|
dibptr->io_interface (dibptr, DSETMASK, /* send the SET MASK signal to the device */
|
|
write_value); /* and supply the new mask value */
|
|
|
|
if (outbound & INTREQ) /* if an interrupt request was asserted */
|
|
iop_assert_INTREQ (dibptr); /* then set it up */
|
|
|
|
no_response = FALSE; /* at least one device has responded */
|
|
}
|
|
}
|
|
|
|
if (no_response) /* if no devices responded */
|
|
CPX1 |= cpx1_IOTIMER; /* then indicate an I/O timeout */
|
|
}
|
|
|
|
else { /* otherwise a device-specific order is present */
|
|
device_number = device_number & DEVNO_MASK; /* restrict the device number to 0-127 */
|
|
|
|
dprintf (iop_dev, FILTER (device_number) ? DEB_DIO : 0,
|
|
"%s order sent to device number %u\n",
|
|
io_command_name [io_cmd], device_number);
|
|
|
|
dibptr = devs [device_number]; /* get the device information block pointer */
|
|
|
|
if (dibptr == NULL) /* if the device not present */
|
|
CPX1 |= cpx1_IOTIMER; /* then indicate an I/O timeout on access */
|
|
|
|
else { /* otherwise call the device interface */
|
|
outbound = /* with the indicated signal and write value */
|
|
dibptr->io_interface (dibptr, cmd_to_signal [io_cmd],
|
|
write_value);
|
|
|
|
if (outbound & INTREQ) /* if an interrupt request was asserted */
|
|
iop_assert_INTREQ (dibptr); /* then set it up */
|
|
|
|
if (outbound & SRn) /* if a service request was asserted */
|
|
mpx_assert_SRn (dibptr); /* then set it up */
|
|
|
|
if (io_cmd == ioRIN /* if this a "Reset Interrupt" order */
|
|
&& dibptr->interrupt_priority != INTPRI_UNUSED) { /* and the interrupt priority is valid */
|
|
irq = 1 << dibptr->interrupt_priority; /* then calculate the device bit */
|
|
|
|
if ((iop_interrupt_request_set & irq) == 0) /* if no request is pending for this device */
|
|
interrupt_poll_set &= ~irq; /* then clear the associated poll bit */
|
|
}
|
|
}
|
|
}
|
|
|
|
return IODATA (outbound); /* return the outbound data value */
|
|
}
|
|
|
|
|
|
/* Request an interrupt.
|
|
|
|
This routine is called by device interfaces to request an external interrupt.
|
|
It corresponds in hardware to asserting the INTREQ signal. The routine sets
|
|
the request and poll set bits corresponding to the interrupt priority number.
|
|
*/
|
|
|
|
void iop_assert_INTREQ (DIB *dibptr)
|
|
{
|
|
uint32 irq;
|
|
|
|
dprintf (iop_dev, FILTER (dibptr->device_number) ? DEB_IRQ : 0,
|
|
"Device number %u asserted INTREQ at priority %u\n",
|
|
dibptr->device_number, dibptr->interrupt_priority);
|
|
|
|
if (dibptr->interrupt_priority != INTPRI_UNUSED) { /* if the interrupt priority is valid */
|
|
irq = 1 << dibptr->interrupt_priority; /* then calculate the corresponding priority bit */
|
|
|
|
iop_interrupt_request_set |= irq; /* set the request */
|
|
interrupt_poll_set |= irq; /* and the poll bits */
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* IOP local SCP support routines */
|
|
|
|
|
|
|
|
/* Set the trace omission filter.
|
|
|
|
If the "value" parameter is 1, the filter array bits corresponding to the
|
|
device number(s) in the buffer referenced by the "cptr" parameter are set to
|
|
exclude those devices from the trace listing. If the "value" parameter is 0,
|
|
the filter array is reset to include all devices. The unit and descriptor
|
|
pointer parameters are not used.
|
|
|
|
Each bit of the four, 32-bit filter array elements corresponds to a device
|
|
number from 0-127, with the LSB of the first element representing device 0,
|
|
and the MSB of the last element representing device 127. A set bit enables
|
|
tracing of that device. The filter starts out with all bits set, implying
|
|
that all devices are traced. Specifying device numbers to filter out clears
|
|
the corresponding bits.
|
|
|
|
Example filter commands:
|
|
|
|
SET IOP FILTER=3 -- omit tracing for device 3.
|
|
SET IOP FILTER=4;7-9;11 -- omit tracing for devices 4, 7, 8, 9, and 11.
|
|
SET IOP FILTER=ALL -- omit tracing for all devices
|
|
SET IOP NOFILTER -- restore tracing for all devices
|
|
|
|
On entry, the "cptr" parameter points to the first character of the range
|
|
specification, which may be either a semicolon-separated list of device
|
|
number ranges or the keyword ALL. Each range is parsed and added to the new
|
|
filter array. Once the entire array has been set, it is copied over the old
|
|
filter. If an error occurs during parsing, the original filter set is not
|
|
disturbed.
|
|
*/
|
|
|
|
static t_stat iop_set_filter (UNIT *uptr, int32 value, CONST char *cptr, void *desc)
|
|
{
|
|
CONST char *tptr;
|
|
char *mptr;
|
|
t_addr dev, low, high;
|
|
t_stat result = SCPE_OK;
|
|
uint32 new_filter [4] = { TRACE_ALL, TRACE_ALL, TRACE_ALL, TRACE_ALL };
|
|
|
|
if (value == 1) { /* if we are setting the filter */
|
|
if ((cptr == NULL) || (*cptr == '\0')) /* then if a line range was not supplied */
|
|
return SCPE_MISVAL; /* then report a "Missing value" error */
|
|
|
|
mptr = (char *) malloc (strlen (cptr) + 2); /* allocate space for the string, a semicolon, and a NUL */
|
|
|
|
if (mptr == NULL) /* if the allocation failed */
|
|
return SCPE_MEM; /* report memory exhaustion */
|
|
|
|
strcpy (mptr, cptr); /* copy over the existing command string */
|
|
tptr = strcat (mptr, ";"); /* and append a semicolon to make parsing easier */
|
|
|
|
while (*tptr) { /* parse the command string until it is exhausted */
|
|
tptr = get_range (NULL, tptr, &low, &high, /* get a semicolon-separated device number range */
|
|
10, 127, ';'); /* in radix 10 with a maximum value of 127 */
|
|
|
|
if (tptr == NULL || low > 127 || high > 127) { /* if a parsing error occurred or a number was out of range */
|
|
result = SCPE_ARG; /* then report an "Invalid argument" error */
|
|
break; /* and quit at this point */
|
|
}
|
|
|
|
else for (dev = low; dev <= high; dev++) /* otherwise loop through the range of device numbers */
|
|
new_filter [dev / 32] &= ~(1 << dev % 32); /* and clear each corresponding bit in the array */
|
|
}
|
|
|
|
free (mptr); /* deallocate the temporary string */
|
|
}
|
|
|
|
else if (cptr != NULL) /* otherwise we are clearing the filter */
|
|
return SCPE_2MARG; /* and no arguments are allowed or needed */
|
|
|
|
if (result == SCPE_OK) { /* if the filter assignment was successful */
|
|
filter [0] = new_filter [0]; /* then copy */
|
|
filter [1] = new_filter [1]; /* the new filter set */
|
|
filter [2] = new_filter [2]; /* in place of */
|
|
filter [3] = new_filter [3]; /* the current filter set */
|
|
}
|
|
|
|
return result; /* return the result of the command */
|
|
}
|
|
|
|
|
|
/* Show the omission filter.
|
|
|
|
The device numbers in the filter array are printed as a semicolon-separated
|
|
list on the stream designated by the "st" parameter. The "uptr", "value",
|
|
and "desc" parameters are not used.
|
|
|
|
Ranges are printed where possible to shorten the output. This is
|
|
accomplished by tracking the starting and ending device numbers of a range of
|
|
bits in the filter and then printing that range when a device number bit not
|
|
in the filter is encountered.
|
|
*/
|
|
|
|
static t_stat iop_show_filter (FILE *st, UNIT *uptr, int32 value, CONST void *desc)
|
|
{
|
|
int32 group, low, high;
|
|
uint32 test_filter;
|
|
t_bool first = TRUE, in_range = FALSE;
|
|
|
|
low = 0; /* initialize the current starting value */
|
|
|
|
for (group = 0; group < 4; group++) { /* the filter values are stored in four elements */
|
|
test_filter = filter [group]; /* get the set of devices from current element */
|
|
|
|
for (high = group * 32; high < group * 32 + 32; high++) { /* loop through the represented device numbers */
|
|
if ((test_filter & 1) == 0) { /* if the current device is filtered out */
|
|
in_range = TRUE; /* then accumulate the omission range */
|
|
}
|
|
|
|
else if (in_range) { /* otherwise if an omission range was accumulated */
|
|
if (first) { /* then if this is the first range to be printed */
|
|
fputs ("filter=", st); /* then print a header to start */
|
|
first = FALSE;
|
|
}
|
|
|
|
else /* otherwise this is not the first range to be printed */
|
|
fputc (';', st); /* so print a separator after the prior range */
|
|
|
|
if (low == high - 1) /* if the range is empty */
|
|
fprintf (st, "%d", low); /* then print the single device number */
|
|
|
|
else /* otherwise a range was established */
|
|
fprintf (st, "%d-%d", low, high - 1); /* so print the starting and ending device numbers */
|
|
|
|
in_range = FALSE; /* start a new range */
|
|
low = high + 1; /* from this device number onward */
|
|
}
|
|
|
|
else /* otherwise we are between ranges */
|
|
low = low + 1; /* so increment the current starting value */
|
|
|
|
test_filter = test_filter >> 1; /* shift the next filter bit into place for testing */
|
|
}
|
|
}
|
|
|
|
if (first == TRUE) /* if there is only a single range */
|
|
if (in_range) /* then if it's an omission range */
|
|
fprintf (st, "filter=%d-127\n", low); /* then report it */
|
|
|
|
else /* otherwise it's an inclusion range */
|
|
fputs ("no filter\n", st); /* so report that no devices are filtered out */
|
|
|
|
else /* otherwise one or more ranges has been printed */
|
|
fputc ('\n', st); /* so add a line terminator */
|
|
|
|
return SCPE_OK;
|
|
}
|