/* hp2100_cpu6.c: HP 1000 RTE-6/VM OS instructions Copyright (c) 2006-2017, 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. CPU6 RTE-6/VM OS instructions 22-Jul-17 JDB Renamed "intaddr" to CIR 10-Jul-17 JDB Renamed the global routine "iogrp" to "cpu_iog" 07-Jul-17 JDB Changed "iotrap" from uint32 to t_bool 27-Mar-17 JDB Expanded comments to describe instruction encoding 17-Jan-17 JDB Revised to use tprintf and TRACE_OPND for debugging 05-Aug-16 JDB Renamed the P register from "PC" to "PR" 17-May-16 JDB Set local variable instead of call parameter for .SIP test 24-Dec-14 JDB Added casts for explicit downward conversions 18-Mar-13 JDB Use MP abort handler declaration in hp2100_cpu.h 09-May-12 JDB Separated assignments from conditional expressions 29-Oct-10 JDB DMA channels renamed from 0,1 to 1,2 to match documentation 18-Sep-08 JDB Corrected .SIP debug formatting 11-Sep-08 JDB Moved microcode function prototypes to hp2100_cpu1.h 05-Sep-08 JDB Removed option-present tests (now in UIG dispatchers) 26-Jun-08 JDB Rewrote device I/O to model backplane signals 27-Nov-07 JDB Implemented OS instructions 26-Sep-06 JDB Created Primary references: - HP 1000 M/E/F-Series Computers Technical Reference Handbook (5955-0282, Mar-1980) - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation (92851-90001, Mar-1981) - Macro/1000 Reference Manual (92059-90001, Dec-1992) The RTE-6/VM Operating System Instructions were added to accelerate certain time-consuming operations of the RTE-6/VM operating system, HP product number 92084A. Microcode was available for the E- and F-Series; the M-Series used software equivalents. The opcodes reside in the range 105340-105357. The encodings are: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 0 0 0 | $LIBR +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | address of the Temporary Data Block or zero | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The $LIBR instruction saves the data contained in the Temporary Data Block of a reentrant subroutine or turns the interrupt system off for a privileged subroutine. The parameter points to the TDB or is zero, respectively. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 0 0 1 | $LIBX +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | address of the subroutine entry point | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The $LIBX instruction restores the data contained in the Temporary Data Block of a reentrant subroutine or turns the interrupt system on for a privileged subroutine and then returns to the routine's caller. The parameter points to the subroutine entry point, which contains the return address. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 0 1 0 | .TICK +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if an EQT timed out : P+1 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if an EQT did not time out : P+2 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The .TICK instruction increments the timeout counters of a set of EQTs. On entry, the A register contains the address of word 15 of the first EQT, and the B register contains the count of EQTs to process. If an EQT timed out, the instruction returns at P+1 with the A register pointing at word 15 of the EQT, and the B register containing the remaining number of EQTs to process. If no EQT timed out, the instruction returns at P+2 with the A register pointing at word 15 of the EQT following the last one checked, and the B register containing zero. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 0 1 1 | .TNAM +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if name is not found : P+1 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if name is found : P+2 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The .TNAM instruction finds the ID segment address corresponding to a given program name. On entry, the A register contains the address of the keyword block, and the B register contains a pointer to the program name. If the ID segment is found, return is to P+2 with the A register containing the address of word 15 of the ID segment, the B register containing the address of the ID segment, and the E register set to 1 for a short ID segment and 0 for a long ID segment. If the ID segment is not found, return is to P+1 with the registers unspecified. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 1 0 0 | .STIO +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | return address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | first I/O instruction address to configure | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | last I/O instruction address to configure | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .STIO instruction configures a list of I/O instructions. On entry, each parameter following the return address points at an instruction to configure with the select code specified in the A register. The instruction returns with all registers unchanged. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 1 0 1 | .FNW +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | increment address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if word is not found : P+2 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if word is found : P+3 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The .FNW instruction finds a word in a buffer. On entry, the A register contains the value of the word to find, the B register contains the address of the buffer, the X register contains the number of words to compare, and the first parameter points to the increment between words. If the word was found, return is to P+3 with the match address in the B register and the count of remaining comparisons in the X register. If the word was not found, return is to P+2 with the address of the next comparison location in the B register and zero in the X register. The A register is unchanged in either case. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 1 1 0 | .IRT +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | P-register restore address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .IRT instruction is used to return from an interrupt. It restores the A, B, E, O, X, and Y registers from the XSUSP and XI save areas and restores the P register from XSUSP,I into the location specified by the parameter, which will be the jump target of a following UJP or SJP instruction. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 0 | 1 1 1 | .LLS +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | address of the search value | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | address of the offset to the search key | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if the linked list is in error : P+3 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if the search value is not found : P+4 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if the search value is found : P+5 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The .LLS instruction conducts a linked-list search. On entry, the A register contains a pointer to the head of the list, the B register is zero, and the E register is 0 to find a value matching the search value or 1 to find a value greater than the search value. The first parameter points at the value to find, and the second parameter points at the offset within each list entry from the link pointer to the value to compare. The instruction returns to P+3 if the list structure is erroneous (a link has its sign bit set), to P+4 if the search value is not present in the list, or to P+5 if the search value was found. In the latter two cases, the A register points to the link word of the current entry, and the B register points to the link word of the previous entry. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 0 0 0 | .SIP +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if no interrupt is pending : P+1 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if interrupt is pending : P+2 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The .SIP instruction skips if an interrupt is pending. It is used to avoid exiting and then immediately reentering RTE when an interrupt is pending at exit. A pending interrupt must be serviced by executing the .YLD instruction. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 0 0 1 | .YLD +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | point of resumption after interrupt | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .YLD instruction yields control to the trap cell instruction for the pending interrupt. Before transferring control, the P-register set to the value contained in the second instruction word. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 0 1 0 | .CPM +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | first argument address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | second argument address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if argument 1 = argument 2 : P+3 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if argument 1 < argument 2 : P+4 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if argument 1 > argument 2 : P+5 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The .CPM instruction compares the two words pointed to by the parameters and returns to P+3 if the words are equal, to P+4 if word 1 is less than word 2, or to P+5 if word 1 is greater than word 2. The registers are unchanged by this instruction. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 0 1 1 | .ETEQ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .ETEQ instruction sets up the base-page EQT addresses to point at the EQT whose address is contained in the A register. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 0 0 | .ENTN +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | parameter block address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .ENTN instruction transfers the direct addresses of parameters from the calling sequence of a utility subroutine to the parameter block specified. The sequence differs from the .ENTR sequence in that there is no DEF *+n parameter immediately following the call, so the number of parameters specified must match the size of the parameter block. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 0 1 | $OTST +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ : return location if the firmware is not installed : P+1 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ : return location if the firmware is installed : P+2 +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ The $OTST instruction is used to determine programmatically if the OS firmware has been installed. It sets the X-register to the firmware revision code, sets S to 102077 (HLT 77B), and returns to P+2. In hardware, it also sets Y to the RPL switch settings and sets A to the contents of the loader ROM location specified by the B-register. In simulation, these last two actions are not implemented. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 1 0 | .ENTC +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | parameter block address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .ENTC instruction transfers the direct addresses of parameters from the calling sequence of a privileged or reentrant subroutine to the parameter block specified. The sequence differs from the .ENTP sequence in that there is no DEF *+n parameter immediately following the call, so the number of parameters specified must match the size of the parameter block. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 1 1 | .DSPI +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The .DSPI instruction copies the lower six bits of the A register into the display indicator register that controls the A, B, M, T, P, and S LEDs on the front panel of the CPU. This is not simulated. Opcodes 105354-105357 are "dual use" instructions that take different actions, depending on whether they are executed from a trap cell during an interrupt. When executed from a trap cell, they have these encodings: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 0 0 | $DCPC +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The $DCPC instruction is placed in base-page locations 6 and 7 to handle DCPC completion interrupts. +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 0 1 | $MPV +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The $MPV instruction is placed in base-page location 5 to handle memory protect, parity error, and MEM violation interrupts. +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 1 0 | $DEV +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The $DEV instruction is placed in base-page locations corresponding to the select codes of devices whose interrupts are handled by $CIC. +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | 0 0 0 | 1 0 1 | 0 1 1 | 1 0 1 | 1 1 1 | $TBG +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ The $TBG instruction is placed in base-page location corresponding to the select code of the time-base generator interface to handle TBG tick interrupts. */ #include #include "hp2100_defs.h" #include "hp2100_cpu.h" #include "hp2100_cpu1.h" /* Offsets to data and addresses within RTE. */ static const HP_WORD xi = 0001647u; /* XI address */ static const HP_WORD intba = 0001654u; /* INTBA address */ static const HP_WORD intlg = 0001655u; /* INTLG address */ static const HP_WORD eqt1 = 0001660u; /* EQT1 address */ static const HP_WORD eqt11 = 0001672u; /* EQT11 address */ static const HP_WORD pvcn = 0001712u; /* PVCN address */ static const HP_WORD xsusp = 0001730u; /* XSUSP address */ static const HP_WORD dummy = 0001737u; /* DUMMY address */ static const HP_WORD mptfl = 0001770u; /* MPTFL address */ static const HP_WORD eqt12 = 0001771u; /* EQT12 address */ static const HP_WORD eqt15 = 0001774u; /* EQT15 address */ static const HP_WORD vctr = 0002000u; /* VCTR address */ static const HP_WORD CLC_0 = 0004700u; /* CLC 0 instruction */ static const HP_WORD STC_0 = 0000700u; /* STC 0 instruction */ static const HP_WORD CLF_0 = 0001100u; /* CLF 0 instruction */ static const HP_WORD STF_0 = 0000100u; /* STF 0 instruction */ static const HP_WORD SFS_0_C = 0003300u; /* SFS 0,C instruction */ /* RTE communication vector. The instructions depend on a status area and a set of (direct) addresses for communication with RTE. Location 2000 in the system map contains a pointer to the block, which is arranged as follows: Location Label Contents Use -------- ----- --------- ------------------------------------------------- L+00 $DMS BSS 1 DMS status at interrupt L+01 $INT BSS 1 Interrupt system status L+02 INTCD BSS 1 Interrupting select code L+03 DEF $CLCK Address of the TBG handler L+04 DEF $CIC4 Address of the illegal interrupt handler L+05 DEF $CIC2 Address of the normal drivers handler L+06 DEF $SKED Address of the interrupt program scheduler L+07 DEF $RQST Address of the EXEC request processor L+10 DEF $CIC Address of the P-Register location for $PERR L+11 DEF $PERR Address of the parity error processor L+12 DEF $MPER Address of the error routine for $LIBR L+13 DEF $LXND Address of the privileged mode cleanup for $LIBX */ typedef enum { dms_offset = 0, /* DMS status */ int_offset, /* interrupt system status */ sc_offset, /* select code */ clck_offset, /* TBG IRQ handler */ cic4_offset, /* illegal IRQ handler */ cic2_offset, /* device IRQ handler */ sked_offset, /* prog sched IRQ handler */ rqst_offset, /* EXEC request handler */ cic_offset, /* IRQ location */ perr_offset, /* parity error IRQ handler */ mper_offset, /* memory protect IRQ handler */ lxnd_offset /* $LIBX return */ } VECTOR_OFFSETS; /* Save the CPU registers. The CPU registers are saved in the current ID segment in preparation for interrupt handling. Although the RTE base page has separate pointers for the P, A, B, and E/O registers, they are always contiguous, and the microcode simply increments the P-register pointer (XSUSP) to store the remaining values. This routine is called from the trap cell interrupt handlers and from the $LIBX processor. In the latter case, the privileged system interrupt handling is not required, so it is bypassed. In either case, the current map will be the system map when we are called. */ static t_stat cpu_save_regs (t_bool iotrap) { HP_WORD save_area, priv_fence; t_stat reason = SCPE_OK; save_area = ReadW (xsusp); /* addr of PABEO save area */ WriteW (save_area + 0, PR); /* save P */ WriteW (save_area + 1, AR); /* save A */ WriteW (save_area + 2, BR); /* save B */ WriteW (save_area + 3, (E << 15) & SIGN | O & 1); /* save E and O */ save_area = ReadW (xi); /* addr of XY save area */ WriteWA (save_area + 0, XR); /* save X (in user map) */ WriteWA (save_area + 1, YR); /* save Y (in user map) */ if (iotrap) { /* do priv setup only if IRQ */ priv_fence = ReadW (dummy); /* get priv fence select code */ if (priv_fence) { /* privileged system? */ reason = cpu_iog (STC_0 + priv_fence, iotrap); /* STC SC on priv fence */ reason = cpu_iog (CLC_0 + DMA1, iotrap); /* CLC 6 to inh IRQ on DCPC 1 */ reason = cpu_iog (CLC_0 + DMA2, iotrap); /* CLC 7 to inh IRQ on DCPC 2 */ reason = cpu_iog (STF_0, iotrap); /* turn interrupt system back on */ } } return reason; } /* Save the machine state at interrupt. This routine is called from each of the trap cell instructions. Its purpose is to save the complete state of the machine in preparation for interrupt handling. For the MP/DMS/PE interrupt, the interrupting device must not be cleared and the CPU registers must not be saved until it is established that the interrupt is not caused by a parity error. Parity errors cannot be inhibited, so the interrupt may have occurred while running in RTE with the interrupt system off. Saving the registers would overwrite the user's registers that were saved at RTE entry. The state of the interrupt system is determined by executing a SFS 0,C instruction, which increments P if the interrupt system was on, and then turns it off by clearing the flag. Note that the trap cell instructions are dual-use and invoke this routine only when they are executed during interrupts. Therefore, the current map will always be the system map when we are called. */ static t_stat cpu_save_state (uint32 iotrap) { HP_WORD vectors, saved_PR, int_sys_off; t_stat reason; saved_PR = PR; /* save current P register */ reason = cpu_iog (SFS_0_C, iotrap); /* turn interrupt system off */ int_sys_off = (HP_WORD) (PR == saved_PR); /* set flag if already off */ PR = saved_PR; /* restore P in case it bumped */ vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ WriteW (vectors + dms_offset, dms_upd_sr ()); /* save DMS status (SSM) */ WriteW (vectors + int_offset, int_sys_off); /* save int status */ WriteW (vectors + sc_offset, CIR); /* save select code */ WriteW (mptfl, 1); /* show MP is off */ if (CIR != PRO) { /* only if not MP interrupt */ reason = cpu_iog (CLF_0 + CIR, iotrap); /* issue CLF to device */ cpu_save_regs (iotrap); /* save CPU registers */ } return reason; } /* Get the interrupt table entry corresponding to a select code. Return the word in the RTE interrupt table that corresponds to the interrupting select code. Return 0 if the select code is beyond the end of the table. */ static HP_WORD cpu_get_intbl (HP_WORD select_code) { HP_WORD interrupt_table; /* interrupt table (starts with SC 06) */ HP_WORD table_length; /* length of interrupt table */ interrupt_table = ReadW (intba); /* get int table address */ table_length = ReadW (intlg); /* get int table length */ if (select_code - 6 > table_length) /* SC beyond end of table? */ return 0; /* return 0 for illegal interrupt */ else return ReadW (interrupt_table + select_code - 6); /* else return table entry */ } /* RTE-6/VM Operating System Instructions. The OS instructions were added to accelerate certain time-consuming operations of the RTE-6/VM operating system, HP product number 92084A. Microcode was available for the E- and F-Series; the M-Series used software equivalents. Option implementation by CPU was as follows: 2114 2115 2116 2100 1000-M 1000-E 1000-F ------ ------ ------ ------ ------ ------ ------ N/A N/A N/A N/A N/A 92084A 92084A The routines are mapped to instruction codes as follows: Instr. 1000-E/F Description ------ -------- ---------------------------------------------- $LIBR 105340 Enter privileged/reentrant library routine $LIBX 105341 Exit privileged/reentrant library routine .TICK 105342 TBG tick interrupt handler .TNAM 105343 Find ID segment that matches name .STIO 105344 Configure I/O instructions .FNW 105345 Find word with user increment .IRT 105346 Interrupt return processing .LLS 105347 Linked list search .SIP 105350 Skip if interrupt pending .YLD 105351 .SIP completion return point .CPM 105352 Compare words LT/EQ/GT .ETEQ 105353 Set up EQT pointers in base page .ENTN 105354 Transfer parameter addresses (utility) $OTST * 105355 OS firmware self test .ENTC 105356 Transfer parameter addresses (priv/reent) .DSPI 105357 Set display indicator Opcodes 105354-105357 are "dual use" instructions that take different actions, depending on whether they are executed from a trap cell during an interrupt. When executed from a trap cell, they have these actions: Instr. 1000-E/F Description ------ -------- ---------------------------------------------- $DCPC * 105354 DCPC channel interrupt processing $MPV * 105355 MP/DMS/PE interrupt processing $DEV * 105356 Standard device interrupt processing $TBG * 105357 TBG interrupt processing * These mnemonics are recognized by symbolic examine/deposit but are not official HP mnemonics. The default (user microcode) dispatcher will allow the firmware self-test instruction (105355) to execute as NOP. This is because RTE-6/VM will always test for the presence of OS and VMA firmware on E/F-Series machines. If the firmware is not present, then these instructions will return to P+1, and RTE will then HLT 21. This means that RTE-6/VM will not run on an E/F-Series machine without the OS and VMA firmware. However, RTE allows the firmware instructions to be disabled for debugging purposes. If the firmware is present and returns to P+2 but sets the X register to 0, then RTE will use software equivalents. We enable this condition when the OS firmware is enabled (SET CPU VMA), the OS debug flag is set (SET CPU DEBUG=OS), but debug output has been disabled (SET CONSOLE NODEBUG). That is: OS Debug Firmware Debug Output Tracing Self-Test Instruction ======== ===== ====== ======= ===================== disabled x x off NOP enabled clear x off X = revision code enabled set off off X = 0 enabled set on on X = revision code Additional references: - RTE-6/VM OS Microcode Source (92084-18831, revision 8). - RTE-6/VM Technical Specifications (92084-90015, Apr-1983). Implementation notes: 1. The microcode differentiates between interrupt processing and normal execution of the "dual use" instructions by testing the CPU flag. Interrupt vectoring sets the flag; a normal instruction fetch clears it. Under simulation, interrupt vectoring is indicated by the value of the "iotrap" parameter (FALSE = normal instruction, TRUE = trap cell instruction). 2. The operand patterns for .ENTN and .ENTC normally would be coded as "OP_A", as each takes a single address as a parameter. However, because they might also be executed from a trap cell (as $DCPC and $DEV), we cannot assume that P+1 is an address, or we might cause a DM abort when trying to resolve indirects. Therefore, "OP_A" handling is done within each routine, once the type of use is determined. 3. The microcode for .ENTC, .ENTN, .FNW, .LLS, .TICK, and .TNAM explicitly checks for interrupts during instruction execution. In addition, the .STIO, .CPM, and .LLS instructions implicitly check for interrupts during parameter indirect resolution. Because the simulator calculates interrupt requests only between instructions, this behavior is not simulated. 4. The microcode executes certain I/O instructions (e.g., CLF 0) by building the instruction in the IR and executing an IOG micro-order. We simulate this behavior by calling the "cpu_iog" handler with the appropriate instruction, rather than manipulating the I/O system directly, so that we will remain unaffected by any future changes to the underlying I/O simulation structure. 5. The $OTST and .DSPI microcode provides features (reading the RPL switches and boot loader ROM data, loading the display register) that are not simulated. The remaining functions of the $OTST instruction are provided. The .DSPI instruction is a NOP or unimplemented instruction stop. 6. The $LIBX instruction is executed to complete either a privileged or reentrant execution. In the former case, the privileged nest counter ($PVCN) is decremented. In the latter, $PVCN decrement is attempted but the write will trap with an MP violation, as reentrant routines execute with the interrupt system on. RTE will then complete the release of memory allocated for the original $LIBR call. 7. The documentation for the .SIP and .YLD instructions is misleading in several places. Comments in the RTE $SIP source file say that .SIP doesn't return if a "known" interrupt is pending. Actually, .SIP always returns, either to P+1 for no pending interrupt, or to P+2 if one is pending. There is no check for "known" interrupt handlers. The microcode source comments say that the interrupting select code is returned in the B register. Actually, the B register is unchanged. The RTE Tech Specs say that .SIP "services any pending system interrupts." Actually, .SIP only checks for interrupts; no servicing is performed. For .YLD, the microcode comments say that two parameters are passed: the new P value, and the interrupting select code. Actually, only the new P value is passed. The .SIP and .YLD simulations follow the actual microcode rather than the documentation. 8. The "%.0u" print specification in the trace call absorbs the zero "CIR" value parameter without printing when an interrupt is not pending. */ static const OP_PAT op_os [16] = { OP_A, OP_A, OP_N, OP_N, /* $LIBR $LIBX .TICK .TNAM */ OP_A, OP_K, OP_A, OP_KK, /* .STIO .FNW .IRT .LLS */ OP_N, OP_C, OP_KK, OP_N, /* .SIP .YLD .CPM .ETEQ */ OP_N, OP_N, OP_N, OP_N /* .ENTN $OTST .ENTC .DSPI */ }; t_stat cpu_rte_os (uint32 IR, uint32 intrq, t_bool iotrap) { static const char *const no [2] = { "", "no " }; static const char *const not [2] = { "not ", "" }; static const char *const list [3] = { "list error", "not found", "found" }; static const char *const compare [3] = { "equal", "less than", "greater than" }; OPS op; OP_PAT pattern; uint32 i, irq; HP_WORD entry, count, cp, sa, da, ma, eqta; HP_WORD vectors, save_area, priv_fence, eoreg, eqt, key; char test [6], target [6]; t_stat reason = SCPE_OK; entry = IR & 017; /* mask to entry point */ pattern = op_os [entry]; /* get operand pattern */ if (pattern != OP_N) { reason = cpu_ops (pattern, op, intrq); /* get instruction operands */ if (reason != SCPE_OK) /* evaluation failed? */ return reason; /* return reason for failure */ } switch (entry) { /* decode IR<3:0> */ case 000: /* $LIBR 105340 (OP_A) */ if ((op[0].word != 0) || /* reentrant call? */ (mp_control && (ReadW (dummy) != 0))) { /* or priv call + MP on + priv sys? */ if (dms_ump) { /* called from user map? */ dms_viol (err_PC, MVI_PRV); /* privilege violation */ } dms_ump = SMAP; /* set system map */ vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ PR = ReadW (vectors + mper_offset); /* vector to $MPER for processing */ } else { /* privileged call */ if (mp_control) { /* memory protect on? */ mp_control = CLEAR; /* turn it off */ reason = cpu_iog (CLF_0, iotrap); /* turn interrupt system off */ WriteW (mptfl, 1); /* show MP is off */ save_area = ReadW (xsusp); /* get addr of P save area */ if (dms_ump) /* user map current? */ WriteWA (save_area, (PR - 2) & VAMASK); /* set point of suspension */ else /* system map current */ WriteW (save_area, (PR - 2) & VAMASK); /* set point of suspension */ } WriteW (pvcn, (ReadW (pvcn) + 1) & DMASK); /* increment priv nest counter */ } break; case 001: /* $LIBX 105341 (OP_A) */ PR = ReadW (op[0].word); /* set P to return point */ count = (ReadW (pvcn) - 1) & DMASK; /* decrement priv nest counter */ WriteW (pvcn, count); /* write it back */ if (count == 0) { /* end of priv mode? */ dms_ump = SMAP; /* set system map */ reason = cpu_save_regs (iotrap); /* save registers */ vectors = ReadW (vctr); /* get address of vectors */ PR = ReadW (vectors + lxnd_offset); /* vector to $LXND for processing */ } break; case 002: /* .TICK 105342 (OP_N) */ do { eqt = (ReadW (AR) + 1) & DMASK; /* bump timeout from EQT15 */ if (eqt != 1) { /* was timeout active? */ WriteW (AR, eqt); /* yes, write it back */ if (eqt == 0) /* did timeout expire? */ break; /* P+1 return for timeout */ } AR = (AR + 15) & DMASK; /* point at next EQT15 */ BR = (BR - 1) & DMASK; /* decrement count of EQTs */ } while ((BR > 0) && (eqt != 0)); /* loop until timeout or done */ if (BR == 0) /* which termination condition? */ PR = (PR + 1) & VAMASK; /* P+2 return for no timeout */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " return location is P+%u (%stimeout)\n", PR, IR, PR - err_PC, no [PR - err_PC - 1]); break; case 003: /* .TNAM 105343 (OP_N) */ E = 1; /* preset flag for not found */ cp = (BR << 1) & DMASK; /* form char addr (B is direct) */ for (i = 0; i < 5; i++) { /* copy target name */ target[i] = (char) ReadB (cp); /* name is only 5 chars */ cp = (cp + 1) & DMASK; } if ((target[0] == '\0') && (target[1] == '\0')) /* if name is null, */ break; /* return immed to P+1 */ key = ReadW (AR); /* get first keyword addr */ while (key != 0) { /* end of keywords? */ cp = ((key + 12) << 1) & DMASK; /* form char addr of name */ for (i = 0; i < 6; i++) { /* copy test name */ test[i] = (char) ReadB (cp); /* name is only 5 chars */ cp = (cp + 1) & DMASK; /* but copy 6 to get flags */ } if (strncmp (target, test, 5) == 0) { /* names match? */ AR = (key + 15) & DMASK; /* A = addr of IDSEG [15] */ BR = key; /* B = addr of IDSEG [0] */ E = (uint32) ((test[5] >> 4) & 1); /* E = short ID segment bit */ PR = (PR + 1) & VAMASK; /* P+2 for found return */ break; } AR = (AR + 1) & DMASK; /* bump to next keyword */ key = ReadW (AR); /* get next keyword */ }; tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " return location is P+%u (%sfound)\n", PR, IR, PR - err_PC, not [PR - err_PC - 1]); break; case 004: /* .STIO 105344 (OP_A) */ count = op[0].word - PR; /* get count of operands */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " instruction count is %u\n", PR, IR, count); for (i = 0; i < count; i++) { ma = ReadW (PR); /* get operand address */ reason = resolve (ma, &ma, intrq); /* resolve indirect */ if (reason != SCPE_OK) { /* resolution failed? */ PR = err_PC; /* IRQ restarts instruction */ break; } WriteW (ma, ReadW (ma) & ~I_DEVMASK | AR); /* set SC into instruction */ PR = (PR + 1) & VAMASK; /* bump to next */ } break; case 005: /* .FNW 105345 (OP_K) */ while (XR != 0) { /* all comparisons done? */ key = ReadW (BR); /* read a buffer word */ if (key == AR) { /* does it match? */ PR = (PR + 1) & VAMASK; /* P+3 found return */ break; } BR = (BR + op[0].word) & DMASK; /* increment buffer ptr */ XR = (XR - 1) & DMASK; /* decrement remaining count */ } /* P+2 not found return */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " return location is P+%u (%sfound)\n", PR, IR, PR - err_PC, not [PR - err_PC - 2]); break; case 006: /* .IRT 105346 (OP_A) */ save_area = ReadW (xsusp); /* addr of PABEO save area */ WriteW (op[0].word, ReadW (save_area + 0)); /* restore P to DEF RTN */ AR = ReadW (save_area + 1); /* restore A */ BR = ReadW (save_area + 2); /* restore B */ eoreg = ReadW (save_area + 3); /* get combined E and O */ E = (eoreg >> 15) & 1; /* restore E */ O = eoreg & 1; /* restore O */ save_area = ReadW (xi); /* addr of XY save area */ XR = ReadWA (save_area + 0); /* restore X (from user map) */ YR = ReadWA (save_area + 1); /* restore Y (from user map) */ reason = cpu_iog (CLF_0, iotrap); /* turn interrupt system off */ WriteW (mptfl, 0); /* show MP is on */ priv_fence = ReadW (dummy); /* get priv fence select code */ if (priv_fence) { /* privileged system? */ reason = cpu_iog (CLC_0 + priv_fence, iotrap); /* CLC SC on priv fence */ reason = cpu_iog (STF_0 + priv_fence, iotrap); /* STF SC on priv fence */ if (cpu_get_intbl (DMA1) & SIGN) /* DCPC 1 active? */ reason = cpu_iog (STC_0 + DMA1, iotrap); /* STC 6 to enable IRQ on DCPC 1 */ if (cpu_get_intbl (DMA2) & SIGN) /* DCPC 2 active? */ reason = cpu_iog (STC_0 + DMA2, iotrap); /* STC 7 to enable IRQ on DCPC 2 */ } break; case 007: /* .LLS 105347 (OP_KK) */ AR = AR & ~SIGN; /* clear sign bit of A */ while ((AR != 0) && ((AR & SIGN) == 0)) { /* end of list or bad list? */ key = ReadW ((AR + op[1].word) & VAMASK); /* get key value */ if ((E == 0) && (key == op[0].word) || /* for E = 0, key = arg? */ (E != 0) && (key > op[0].word)) /* for E = 1, key > arg? */ break; /* search is done */ BR = AR; /* B = last link */ AR = ReadW (AR); /* A = next link */ } if (AR == 0) /* exhausted list? */ PR = (PR + 1) & VAMASK; /* P+4 arg not found */ else if ((AR & SIGN) == 0) /* good link? */ PR = (PR + 2) & VAMASK; /* P+5 arg found */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " return location is P+%u (%s)\n", PR, IR, PR - err_PC, list [PR - err_PC - 3]); break; case 010: /* .SIP 105350 (OP_N) */ reason = cpu_iog (STF_0, iotrap); /* turn interrupt system on */ irq = calc_int (); /* check for interrupt requests */ reason = cpu_iog (CLF_0, iotrap); /* turn interrupt system off */ if (irq) /* was interrupt pending? */ PR = (PR + 1) & VAMASK; /* P+2 return for pending IRQ */ tprintf (cpu_dev, TRACE_OPND, (irq ? OPND_FORMAT " return location is P+2 (pending), CIR %02o\n" : OPND_FORMAT " return location is P+1 (not pending)%.0u\n"), PR, IR, irq); break; case 011: /* .YLD 105351 (OP_C) */ PR = op[0].word; /* pick up point of resumption */ reason = cpu_iog (STF_0, iotrap); /* turn interrupt system on */ ion_defer = FALSE; /* kill defer so irq occurs immed */ break; case 012: /* .CPM 105352 (OP_KK) */ if (INT16 (op[0].word) > INT16 (op[1].word)) PR = (PR + 2) & VAMASK; /* P+5 arg1 > arg2 */ else if (INT16 (op[0].word) < INT16 (op[1].word)) PR = (PR + 1) & VAMASK; /* P+4 arg1 < arg2 */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " return location is P+%u (%s)\n", PR, IR, PR - err_PC, compare [PR - err_PC - 3]); break; case 013: /* .ETEQ 105353 (OP_N) */ eqt = ReadW (eqt1); /* get addr of EQT1 */ if (AR != eqt) { /* already set up? */ for (eqta = eqt1; eqta <= eqt11; eqta++) /* init EQT1-EQT11 */ WriteW (eqta, AR++ & DMASK); for (eqta = eqt12; eqta <= eqt15; eqta++) /* init EQT12-EQT15 */ WriteW (eqta, AR++ & DMASK); /* (not contig with EQT1-11) */ } AR = AR & DMASK; /* ensure wraparound */ break; case 014: /* .ENTN/$DCPC 105354 (OP_N) */ if (iotrap) { /* in trap cell? */ reason = cpu_save_state (iotrap); /* DMA interrupt */ AR = cpu_get_intbl (CIR) & ~SIGN; /* get intbl value and strip sign */ goto DEVINT; /* vector by intbl value */ } else { /* .ENTN instruction */ ma = (PR - 2) & VAMASK; /* get addr of entry point */ ENTX: /* enter here from .ENTC */ reason = cpu_ops (OP_A, op, intrq); /* get instruction operand */ da = op[0].word; /* get addr of 1st formal */ count = ma - da; /* get count of formals */ sa = ReadW (ma); /* get addr of 1st actual */ WriteW (ma, (sa + count) & VAMASK); /* adjust return point to skip actuals */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " parameter count is %u\n", PR, IR, count); for (i = 0; i < count; i++) { /* parameter loop */ ma = ReadW (sa); /* get addr of actual */ sa = (sa + 1) & VAMASK; /* increment address */ reason = resolve (ma, &ma, intrq); /* resolve indirect */ if (reason != SCPE_OK) { /* resolution failed? */ PR = err_PC; /* irq restarts instruction */ break; } WriteW (da, ma); /* put addr into formal */ da = (da + 1) & VAMASK; /* increment address */ } if (entry == 016) /* call was .ENTC? */ AR = sa; /* set A to return address */ } break; case 015: /* $OTST/$MPV 105355 (OP_N) */ if (iotrap) { /* in trap cell? */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " entry is for a %s\n", PR, IR, (mp_viol & SIGN ? "parity error" : (mp_mevff == SET ? "dynamic mapping violation" : "memory protect violation"))); reason = cpu_save_state (iotrap); /* MP/DMS/PE interrupt */ vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ if (mp_viol & SIGN) { /* parity error? */ WriteW (vectors + cic_offset, PR); /* save point of suspension in $CIC */ PR = ReadW (vectors + perr_offset); /* vector to $PERR for processing */ } else { /* MP/DMS violation */ cpu_save_regs (iotrap); /* save CPU registers */ PR = ReadW (vectors + rqst_offset); /* vector to $RQST for processing */ } } else { /* self-test instruction */ YR = 0000000; /* RPL switch (not implemented) */ AR = 0000000; /* LDR [B] (not implemented) */ SR = 0102077; /* test passed code */ PR = (PR + 1) & VAMASK; /* P+2 return for firmware OK */ if (cpu_dev.dctrl & DEBUG_NOOS) /* if the OS debug flag is set */ XR = 0; /* then return rev 0 so that RTE won't use microcode */ else /* otherwise */ XR = 010; /* return firmware revision code 10B */ tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " return location is P+%u (firmware %sinstalled)\n", PR, IR, PR - err_PC, not [PR - err_PC - 1]); } break; case 016: /* .ENTC/$DEV 105356 (OP_N) */ if (iotrap) { /* in trap cell? */ reason = cpu_save_state (iotrap); /* device interrupt */ AR = cpu_get_intbl (CIR); /* get interrupt table value */ DEVINT: vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ if (INT16 (AR) < 0) /* negative (program ID)? */ PR = ReadW (vectors + sked_offset); /* vector to $SKED for processing */ else if (AR > 0) /* positive (EQT address)? */ PR = ReadW (vectors + cic2_offset); /* vector to $CIC2 for processing */ else /* zero (illegal interrupt) */ PR = ReadW (vectors + cic4_offset); /* vector to $CIC4 for processing */ } else { /* .ENTC instruction */ ma = (PR - 4) & VAMASK; /* get addr of entry point */ goto ENTX; /* continue with common processing */ } break; case 017: /* .DSPI/$TBG 105357 (OP_N) */ if (iotrap) { /* in trap cell? */ reason = cpu_save_state (iotrap); /* TBG interrupt */ vectors = ReadW (vctr); /* get address of vectors (in SMAP) */ PR = ReadW (vectors + clck_offset); /* vector to $CLCK for processing */ } else /* .DSPI instruction */ reason = STOP (cpu_ss_unimpl); /* not implemented yet */ break; } return reason; }