944 lines
48 KiB
C
944 lines
48 KiB
C
/* hp3000_iop.c: HP 3000 30003B I/O Processor simulator
|
|
|
|
Copyright (c) 2016-2019, 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
|
|
|
|
18-Feb-19 JDB Continue iop_poll if first interface returns INTPOLLOUT
|
|
10-Oct-16 JDB Renumbered debug flags to start at bit 0
|
|
03-Sep-16 JDB Added "iop_assert_PFWARN" to warn devices of power loss
|
|
01-Aug-16 JDB Added "iop_reset" to initialize the IOP
|
|
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 instructions, 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"
|
|
#include "hp3000_mem.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.
|
|
*/
|
|
|
|
#define DEB_DIO (1u << 0) /* trace direct I/O commands */
|
|
#define DEB_IRQ (1u << 1) /* 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_reset (DEVICE *dptr);
|
|
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 */
|
|
&iop_reset, /* 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).
|
|
|
|
The value of the IOA register is returned. This is zero unless a device
|
|
requesting an interrupt has been acknowledged but not yet serviced, in which
|
|
case the value is the device number.
|
|
*/
|
|
|
|
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. If any other interfaces are asserting INTREQ, the poll continues
|
|
with the next-highest-priority interface in the chain.
|
|
|
|
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.
|
|
|
|
2. An interface that asserted INTREQ should respond to an INTPOLL by
|
|
asserting either INTACK or INTPOLLOUT. If it does neither, then the
|
|
interface is inhibiting lower-priority interrupts but not performing an
|
|
interrupt itself. We simulate this by clearing the request set bit but
|
|
not the poll set bit.
|
|
*/
|
|
|
|
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 */
|
|
|
|
else do { /* otherwise poll for an interrupt request */
|
|
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 was granted */
|
|
return 0; /* then return */
|
|
else /* otherwise */
|
|
iop_interrupt_request_set &= ~priority_mask; /* clear the request */
|
|
|
|
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);
|
|
|
|
break; /* stop the poll at this interface */
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
else { /* otherwise the interface inhibited the poll */
|
|
IOA = 0; /* so clear the device number */
|
|
|
|
dprintf (iop_dev, FILTER (dibptr->device_number) ? DEB_IRQ : 0,
|
|
"Device number %u inhibited INTPOLLIN at priority %u\n",
|
|
dibptr->device_number, ipn);
|
|
|
|
break; /* stop the poll at this interface */
|
|
}
|
|
}
|
|
|
|
while (iop_interrupt_request_set != 0); /* continue polling while requests remain */
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/* Warn devices of an impending power failure.
|
|
|
|
This routine is called by the POWER FAIL command to send a warning
|
|
to all devices that power is about to fail. It corresponds in hardware to
|
|
asserting the PFWARN signal. Devices may process or ignore the signal as
|
|
appropriate. If the device returns the INTREQ signal, an interrupt is
|
|
requested.
|
|
*/
|
|
|
|
void iop_assert_PFWARN (void)
|
|
{
|
|
uint32 devno;
|
|
DIB *dibptr;
|
|
SIGNALS_DATA outbound;
|
|
|
|
for (devno = 0; devno <= DEVNO_MAX; devno++) { /* loop through the device number list */
|
|
dibptr = devs [devno]; /* and get the next device information block pointer */
|
|
|
|
if (dibptr != NULL) { /* if this device is defined */
|
|
outbound = /* then send the PFWARN signal to the device interface */
|
|
dibptr->io_interface (dibptr, PFWARN, 0);
|
|
|
|
if (outbound & INTREQ) /* if the device requested an interrupt */
|
|
iop_assert_INTREQ (dibptr); /* then set it up */
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
/* IOP local SCP support routines */
|
|
|
|
|
|
|
|
/* Device reset routine.
|
|
|
|
This routine is called for a RESET or RESET IOP command. It is the
|
|
simulation equivalent of the IORESET signal, which is asserted by the front
|
|
panel LOAD and DUMP switches.
|
|
|
|
|
|
Implementation notes:
|
|
|
|
1. In hardware, IORESET clears flip-flops associated with the state machines
|
|
that implement the interrupt poll, SO/SI handshake, and multiplexer
|
|
channel access. In simulation, these are all represented by function
|
|
calls and, as such, are atomic. Therefore, the only state variable that
|
|
IORESET clears is the external interrupt flip-flop, which is implemented
|
|
as its respective bit in the CPX1 register rather than as a separate
|
|
variable. Setting IOA to 0 and calling iop_initialize clears this bit;
|
|
it also sets up the devs array, which is used by the POWER FAIL command.
|
|
|
|
2. In hardware, IORESET also clears the IOP address parity error, system
|
|
parity error, and illegal address flip-flops. However, these exist only
|
|
to assert XFERERROR to devices. In simulation, XFERERROR is sent to a
|
|
device interface when the initiating condition is detected by the
|
|
multiplexer channel, so these are not represented by state variables.
|
|
*/
|
|
|
|
static t_stat iop_reset (DEVICE *dptr)
|
|
{
|
|
IOA = 0; /* clear the I/O Address register and initialize */
|
|
iop_initialize (); /* which clears the external interrupt flip-flop */
|
|
|
|
return SCPE_OK;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|