/* 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 | |
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. | |
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; | |
} | |
/* 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; | |
} |