/* hp2100_mem.c: HP 21xx/1000 Main Memory/Memory Expansion Module/Memory Protect simulator Copyright (c) 1993-2016, Robert M. Supnik Copyright (c) 2017-2018, J. David Bryan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the names of the authors shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from the authors. Main memory 12615A/12839A/12885A Core Memory Subsystems 2102B MOS Memory Subsystem MEM 12731A Memory Expansion Module MP 12581A/12892B Memory Protect 02-Aug-18 JDB Added MEM device 30-Jul-18 JDB Renamed "iop_sp" to "SPR" (stack pointer register) 20-Jul-18 JDB Split out from hp2100_cpu.c References: - HP 1000 M/E/F-Series Computers Technical Reference Handbook (5955-0282, March 1980) - HP 1000 M/E/F-Series Computers Engineering and Reference Documentation (92851-90001, March 1981) - HP 1000 M/E/F-Series Computers I/O Interfacing Guide (02109-90006, September 1980) - 12892B Memory Protect Installation Manual (12892-90007, June 1978) - 2100A Computer Reference Manual (02100-90001, December 1971) - Central Processor Options Computer Maintenance Course Student's Manual Volumes V, VI, VII, VIII, & IX (5950-8707, April 1969) This module simulates the HP 12615A and 12839A core memory subsystems for the 2116 and 2115/2114, respectively, the 12885A core memory subsystem for the 2100, and the 2102B MOS memory subsystem for the 1000 M/E/F-Series CPUs. Main memory is implemented as a dynamically allocated array, "M", of MEMORY_WORD words. The MEMORY_WORD type is a 16-bit unsigned type, corresponding with the 16-bit main memory word of the HP 21xx/1000. The largest supported memory size (one megaword for the HP 1000) is allocated when the simulator is started, while the configured memory size for the current CPU is kept in the "mem_size" variable. Installed memory sizes may range from 4K words to 1M words. HP 21xx and 1000 CPUs address a maximum of 32K words with 15-bit addresses. This is the logical address space. 1000-series machines may employ an optional Memory Expansion Module to map the logical address space anywhere with a 1M-word physical memory on a 1K-per-page basis. For all machines, reads to addresses outside of installed memory return all-zeros worda, and writes outside of memory are ignored. Neither operation causes an error. The core memory machines (2114, 2115, 2116, and 2100) have a protected area of memory where a binary loader program may be stored. The protected loader area resides in the last 64 words of installed memory and is normally protected against reading and writing; as with non-existent memory, reads returns all zeros words, and writes are ignored. The loader is unprotected by a switch on the CPU front panel so that it may be executed, typically to bootstrap a system from paper tape, magnetic tape, or disc. The loader is automatically protected when the machine executes a HLT instruction. It may also be protected manually through the front panel. In simulation, loader protection is controlled by the "mem_end" variable. When it is equal to "mem_size", the loader is unprotected and available for execution. When it is less than "mem_size", the loader is protected, and memory logically ends at the "mem_end" address. This module provides routines to read and write memory words and bytes. All memory accesses are classified as to the type of the access, which determines the mapping mode and protection applied. Utility routines to initialize, zero, and copy loaders to and from memory are also supplied. This module also simulates the 12731A Memory Expansion Module for the 1000 M/E/F-Series machines. The MEM provides mapping of the 32 1K-word logical memory pages into a one-megaword physical memory. Four separate 32-page maps are provided: system, user, DCPC port A (used by channel 1), and DCPC port B (used by channel 2). The MEM is controlled by the associated Dynamic Mapping System instructions. While enabled, all programmed memory accesses are translated via the system or user map, depending on which is currently enabled, and all DCPC accesses are translated through one of the two port maps, depending on which channel is making the access. In addition, page 0 (the base page) accesses have an additional translation step. A base page fence separates a mapped portion from an unmapped potion in the system and user maps. The mapped portion is mapped to the physical page that resides in the first map register. The unmapped portion is not mapped and accesses physical page 0. A MEM setting controls whether the mapped portion is above or below the fence. Each map page may be protected against reading or writing. Write protection also extends to executing jump instructions that target the page. Attempting a protected access results in a MEM violation, which is handled by the Memory Protect card. If MP is enabled, a MEM violation causes an interrupt on select code 05; if MP is disabled, no violation occurs, and the read or write proceeds normally. MP and MEM violations are distinguished by executing an SFS 05 instruction, which skips for MEM violations but not for MP violations. Read and write protections are ignored for DCPC accesses. In addition, MEM violations also occur for attempts to write into the unmapped portion of the base page (i.e., to physical page 0), as well as attempts to execute privileged DMS instructions (i.e., those that load any of the map registers). The MEM status and violation registers reflect the current status of the MEM and the last violation, if any. They are formatted as follows. MEM Status Register: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | I | M | E | U | P | B | base page fence address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: I = MEM was disabled/enabled (0/1) at last interrupt M = System/user map (0/1) was selected at last interrupt E = MEM is disabled/enabled (0/1) currently U = System/user map (0/1) is selected currently P = Protected mode is disabled/enabled (0/1) currently B = Base-page portion mapped is above/below (0/1) the fence MEM Violation Register: 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | R | W | B | P | - - - - | S | E | M | page address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: R = Read violation W = Write violation B = Base-page violation P = Privileged instruction violation S = ME bus disabled/enabled (0/1) at violation E = MEM disabled/enabled (0/1) at violation M = System/user map (0/1) selected at violation The MEM card has four hardware configuration jumpers: W1 - configure for 1000 M-Series (A) or 1000 E/F-Series (B) W2 - normal operation (IN) or factory test (OUT) W3 - factory test (IN) or normal operation (OUT) W4 (RME) - MEM remains in the system map after IAK for IOG trap instruction (A) or returns to the prior map (B) These jumpers are not simulated. Instead, the simulation behaves as though W1 is set correctly for the current CPU type, W2 is IN, W3 is OUT, and W4 is set to the A position. This module also simulates the 12581A/12892B Memory Protect accessories for the 2116 and 1000 M/E/F-Series, respectively, and the memory protect feature that is standard equipment for the 2100. MP is addressed via select code 05 and provides a fence register that holds the address of the start of unprotected memory and a violation register that holds the address of the instruction that has caused a memory protect violation. In hardware, if the Memory Protect accessory is installed and enabled, I/O operations to select codes other than 01 are prohibited. Also, in combination with the MPCK micro-order, MP validates the M-register contents (memory address) against the memory protect fence. If a violation occurs, an I/O instruction or memory write is inhibited, and a memory read returns invalid data. In simulation, MP violations are usually detected automatically when the "mem_write" routine is called to write to memory or the "cpu_iog" routine is called to execute an I/O instruction. A few instruction executors detect MP violations explicitly and call the "mp_violation" routine. If MP is enabled, the routine sets the MP flag and then calls "cpu_microcode_abort" to abort the instruction. That routine executes a "longjmp" to the abort handler, which is outside of and precedes the instruction execution loop. An MP interrupt (SC 05) is qualified by "interrupt_system" but not by "cpu_interrupt_enable". If the interrupt system is off when an MP violation is detected, the violating instruction will be aborted, even though no interrupt occurs. In this case, neither the flag nor the flag buffer are set. MP is controlled by I/O instructions directed to select code 05, as follows. Output Data Word format (OTA and OTB): 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | starting address of unprotected memory | fence +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Input Data Word formats (LIA, LIB, MIA, and MIB): 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 | violating instruction address | MP violation +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 1 | violating instruction address | PE violation +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ After setting the fence regiater with an OTA 05 or OTB 05 instruction, MP is enabled by an STC 05. MP cannot be disabled programmatically; it is disabled only by a violation. The SFS 05 and SFC 05 instructions test the Memory Expansion Violation flip-flop, not the MP flag flip-flop. The MEV flip-flop is set for a MEM violation and is clear for an MP violation. The 12892B card has six hardware configuration jumpers: W3 (HLTPE) - parity violation register clocked when an error occurs (OUT) or not clocked when error an occurs and the CPU switch is in the HLT PE position (IN) W4 (MX) - timing is for an E/F-Series (OUT) or for an M-Series (IN) W5 (JSB) - JSB to locations 0 and 1 are prohibited (OUT) or permitted (IN) W6 (INT) - interrupts are enabled immediately if MP is enabled (OUT) or only after three levels of indirection (IN) W7 (SEL1) - permit I/O only to select code 01 (OUT) or to all select codes (IN) W8 (RME) - MEM remains in the system map after IAK for IOG trap instruction (OUT) or returns to the prior map (IN) In simulation, jumpers W5, W6, and W7 may be set via the SCP command line; the default (normal) positions are W5 IN, W6 IN, and W7 OUT. Jumpers W3, W4, and W8 are not simulated. Instead, the simulation behaves as though W3 is OUT, W4 is set correctly for the current CPU type, and W8 is OUT. The jumpers designated as W1 and W2 do not exist. Implementation notes: 1. The terms MEM (Memory Expansion Module), MEU (Memory Expansion Unit), DMI (Dynamic Mapping Instructions), and DMS (Dynamic Mapping System) are used somewhat interchangeably to refer to the logical-to-physical memory address translation option provided on the 1000-Series. DMS consists of the MEM card (12731A) and the DMI firmware (13307A). However, MEM and MEU have been used interchangeably to refer to the mapping card, as have DMI and DMS to refer to the firmware instructions. In this module, MEM routines and state variables are prefixed "meu_" rather than "mem_" to avoid confusion with the main memory symbols. */ #include "hp2100_defs.h" #include "hp2100_cpu.h" #include "hp2100_cpu_dmm.h" /* Main memory instruction masks */ #define IR_MRG (MRG | AB_MASK) /* MRG instructions mask */ #define IR_ISZ 0034000u /* ISZ instruction */ #define IR_STF 0102100u /* STF instruction */ /* Main memory access classification table */ typedef struct { uint32 debug_flag; /* the debug flag for tracing */ const char *name; /* the classification name */ } ACCESS_PROPERTIES; static const ACCESS_PROPERTIES mem_access [] = { /* indexed by ACCESS_CLASS */ /* debug_flag name */ /* ------------ ------------------- */ { TRACE_FETCH, "instruction fetch" }, /* instruction fetch */ { TRACE_DATA, "data" }, /* data access */ { TRACE_DATA, "data" }, /* data access, alternate map */ { TRACE_DATA, "unprotected" }, /* data access, system map */ { TRACE_DATA, "unprotected" }, /* data access, user map */ { TRACE_DATA, "dma" }, /* DMA channel 1, port A map */ { TRACE_DATA, "dma" } /* DMA channel 2, port B map */ }; /* Main memory OS base page addresses */ static const uint32 m64 = 0000040u; /* (DOS) constant -64 address */ static const uint32 p64 = 0000067u; /* (DOS) constant +64 address */ static const uint32 xeqt = 0001717u; /* (RTE) XEQT address */ static const uint32 tbg = 0001674u; /* (RTE) TBG address */ /* Main memory tracing constants */ static const char * const register_values [] = { /* register values, indexed by EOI concatenation */ "e o i", /* E = 0, O = 0, interrupt_system = off */ "e o I", /* E = 0, O = 0, interrupt_system = on */ "e O i", /* E = 0, O = 1, interrupt_system = off */ "e O I", /* E = 0, O = 1, interrupt_system = on */ "E o i", /* E = 1, O = 0, interrupt_system = off */ "E o I", /* E = 1, O = 0, interrupt_system = on */ "E O i", /* E = 1, O = 1, interrupt_system = off */ "E O I" /* E = 1, O = 1, interrupt_system = on */ }; static const char mp_value [] = { /* memory protection value, indexed by mp_control */ '-', /* MP is off */ 'P' /* MP is on */ }; static const char * const register_formats [] = { /* CPU register formats, indexed by is_1000 */ REGA_FORMAT " A %06o, B %06o, ", /* is_1000 = FALSE format */ REGA_FORMAT " A %06o, B %06o, X %06o, Y %06o, " /* is_1000 = TRUE format */ }; static const char * const mp_mem_formats [] = { /* MP/MEM register formats, indexed by is_1000 */ REGB_FORMAT " MPF %06o, MPV %06o\n", /* is_1000 = FALSE format */ REGB_FORMAT " MPF %06o, MPV %06o, MES %06o, MEV %06o\n" /* is_1000 = TRUE format */ }; /* Main memory global state declarations */ uint32 mem_size = 0; /* size of main memory in words */ uint32 mem_end = 0; /* address of the first word beyond installed memory */ /* Main memory local state declarations */ static MEMORY_WORD *M = NULL; /* the pointer to allocated memory */ static DIB *tbg_dibptr = NULL; /* a pointer to the time-base generator DIB (for RTE idle check) */ static t_bool is_1000 = FALSE; /* TRUE if the CPU is a 1000 M/E/F-Series */ /* Memory Expansion Unit command line switches */ #define ALL_MAPMODES (SWMASK ('S') | SWMASK ('U') | \ SWMASK ('P') | SWMASK ('Q')) /* Memory Expansion Unit program limits */ #define MAP_COUNT 4 /* number of maps */ #define REG_COUNT 32 /* number of map registers per map */ /* Memory Expansion Unit program constants */ #define LWA_BASE_PAGE 0001777u /* address of the last word on the base page */ #define MAP_MASK (MAP_COUNT - 1) /* mask to the map selection bits */ #define ALTERNATE_MAP(m) ((MEU_MAP_SELECTOR) ((m) ^ 1)) /* switch to alternate map (user or system) */ /* Memory Expansion Unit state constant declarations */ static const char map_indicator [] = { /* MEU map indicator, indexed by MEU_MAP_SELECTOR */ 'S', /* System_Map */ 'U', /* User_Map */ 'A', /* Port_A_Map */ 'B' /* Port_B_Map */ }; /* MEU Page Map Registers. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | R | W | - - - - | physical page address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #define READ_PROTECTED 0100000u /* (R) read protection bit */ #define WRITE_PROTECTED 0040000u /* (W) write protection bit */ #define MAP_RESERVED 0036000u /* reserved bits */ #define PAGE_MASK PP_MASK /* physical page address mask */ #define NO_PROTECTION 0000000u /* no read/write protection */ #define MAP_PAGE(r) ((r) & PAGE_MASK) /* extract the page number from a map register */ /* MEU status register. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | I | M | E | U | P | B | base page fence address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #define MEST_ENBL_INT 0100000u /* (I) MEM was enabled at last interrupt */ #define MEST_UMAP_INT 0040000u /* (M) User map was selected at last interrupt */ #define MEST_ENABLED 0020000u /* (E) MEM is enabled currently */ #define MEST_USER_MAP 0010000u /* (U) User map is selected currently (set dynamically) */ #define MEST_PROTECTED 0004000u /* (P) Protected mode is enabled currently (set dynamically) */ #define MEST_BELOW 0002000u /* (B) Base page below fence is mapped */ #define MEST_FENCE_MASK 0001777u /* Base page fence mask */ #define MEST_DYNAMIC (MEST_USER_MAP | MEST_PROTECTED) #define MEST_DYNAMIC_IAK (MEST_ENBL_INT | MEST_UMAP_INT) /* MEU Violation Register. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | R | W | B | P | - - - - | S | E | M | page index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #define MEVI_READ 0100000u /* (R) = Read violation */ #define MEVI_WRITE 0040000u /* (W) = Write violation */ #define MEVI_BASE_PAGE 0020000u /* (B) = Base page violation */ #define MEVI_PRIVILEGE 0010000u /* (P) = Privileged instruction violation */ #define MEVI_BUS_ENABLED 0000200u /* (S) = ME bus was enabled at violation */ #define MEVI_MEM_ENABLED 0000100u /* (E) = MEM was enabled at violation */ #define MEVI_USER_MAP 0000040u /* (M) = User map was selected at violation */ #define MEVI_INDEX_MASK 0000037u /* Page register index for which the violation occurred */ /* Memory Expansion Unit global state declarations */ char meu_indicator; /* last map access indicator (S | U | A | B | -) */ uint32 meu_page; /* last physical page number accessed */ /* Memory Expansion Unit local state declarations */ static MEU_MAP_SELECTOR meu_current_map = System_Map; /* the current map */ static t_bool meu_bus_enabled = FALSE; /* TRUE if the memory expansion bus is enabled */ static HP_WORD meu_status = 0; /* the MEM status register */ static HP_WORD meu_violation = 0; /* the MEM violation register */ static HP_WORD meu_maps [MAP_COUNT] [REG_COUNT]; /* the MEM map registers */ /* Memory Expansion Unit local SCP support routine declarations */ static t_stat meu_reset (DEVICE *dptr); /* Memory Expansion Unit local utility routine declarations */ static void dm_violation (HP_WORD violation); static t_bool is_mapped (HP_WORD address); static uint32 map_address (HP_WORD address, MEU_MAP_SELECTOR map, HP_WORD protection); /* Memory Expansion Unit SCP data declarations */ /* Unit list */ static UNIT meu_unit [] = { /* Event Routine Unit Flags Capacity Delay */ /* ------------- ---------- -------- ----- */ { UDATA (NULL, 0, 0) } /* dummy unit */ }; /* Register list. Implementation notes: 1. The REG definitions for the maps must be 17 bits (not 16) to ensure that the map entries are accessed as 32-bit HP_WORDs and not uint16s. */ static REG meu_reg [] = { /* Macro Name Location Radix Width Offset Depth Flags */ /* ------ --------- ---------------------- ----- ----- -------- ---------- ----------------- */ { FLDATA (ENABLED, meu_status, 13) }, { FLDATA (CURMAP, meu_current_map, 0) }, { ORDATA (STATUS, meu_status, 16) }, { ORDATA (VIOL, meu_violation, 16) }, { BRDATA (SMAP, meu_maps [System_Map], 8, 17, REG_COUNT) }, { BRDATA (UMAP, meu_maps [User_Map], 8, 17, REG_COUNT) }, { BRDATA (PAMAP, meu_maps [Port_A_Map], 8, 17, REG_COUNT) }, { BRDATA (PBMAP, meu_maps [Port_B_Map], 8, 17, REG_COUNT) }, { FLDATA (MEBEN, meu_bus_enabled, 0), REG_HRO }, { NULL } }; /* Device descriptor */ DEVICE meu_dev = { "MEM", /* device name */ meu_unit, /* unit array */ meu_reg, /* register array */ NULL, /* modifier array */ 1, /* number of units */ 8, /* address radix */ 1, /* address width */ 1, /* address increment */ 8, /* data radix */ 16, /* data width */ NULL, /* examine routine */ NULL, /* deposit routine */ &meu_reset, /* reset routine */ NULL, /* boot routine */ NULL, /* attach routine */ NULL, /* detach routine */ NULL, /* device information block pointer */ DEV_DIS, /* device flags */ 0, /* debug control flags */ NULL, /* debug flag name table */ NULL, /* memory size change routine */ NULL /* logical device name */ }; /* Memory Protect unit flags */ #define UNIT_V_MP_JSB (UNIT_V_UF + 0) /* MP jumper W5 */ #define UNIT_V_MP_INT (UNIT_V_UF + 1) /* MP jumper W6 */ #define UNIT_V_MP_SEL1 (UNIT_V_UF + 2) /* MP jumper W7 */ #define UNIT_MP_JSB (1 << UNIT_V_MP_JSB) /* 1 = W5 is out */ #define UNIT_MP_INT (1 << UNIT_V_MP_INT) /* 1 = W6 is out */ #define UNIT_MP_SEL1 (1 << UNIT_V_MP_SEL1) /* 1 = W7 is out */ /* Memory Protect violation register. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | P | violating instruction address | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ */ #define MPVR_PARITY_ERROR 0100000u /* (P) parity error violation */ /* Memory Protect global state declarations */ HP_WORD mp_fence = 0; /* MP fence register */ /* Memory Protect local state declarations */ static HP_WORD mp_VR = 0; /* MP violation register */ static FLIP_FLOP mp_control = CLEAR; /* MP control flip-flop */ static FLIP_FLOP mp_flag_buffer = CLEAR; /* MP flag buffer flip-flop */ static FLIP_FLOP mp_flag = CLEAR; /* MP flag flip-flop */ static FLIP_FLOP mp_mevff = CLEAR; /* memory expansion violation flip-flop */ static FLIP_FLOP mp_evrff = SET; /* enable violation register flip-flop */ static FLIP_FLOP mp_enabled = CLEAR; /* MP was enabled at interrupt */ static FLIP_FLOP mp_reenable = CLEAR; /* MP will be reenabled after IAK */ static t_bool mp_mem_changed = TRUE; /* TRUE if the MP or MEM registers have been altered */ static uint32 jsb_bound = 2; /* protected lower bound for JSB */ /* Memory Protect I/O interface routine declarations */ static INTERFACE mp_interface; /* Memory Protect local SCP support routine declarations */ static t_stat mp_set_jsb (UNIT *uptr, int32 value, CONST char *cptr, void *desc); static t_stat mp_reset (DEVICE *dptr); /* Memory Protect local utility routine declarations */ static t_stat mp_service (UNIT *uptr); /* Memory Protect SCP data declarations */ /* Unit list. Implementation notes: 1. The default flags correspond to the following jumper settings: JSB in, INT in, SEL1 out. */ static UNIT mp_unit [] = { /* Event Routine Unit Flags Capacity Delay */ /* ------------- ------------ -------- ----- */ { UDATA (&mp_service, UNIT_MP_SEL1, 0) } }; /* Device information block */ static DIB mp_dib = { &mp_interface, /* the device's I/O interface function pointer */ MPPE, /* the device's select code (02-77) */ 0, /* the card index */ NULL, /* the card description */ NULL /* the ROM description */ }; /* Register list */ static REG mp_reg [] = { /* Macro Name Location Width Flags */ /* ------ -------- -------------- ----- ------- */ { FLDATA (CTL, mp_control, 0) }, { FLDATA (FLG, mp_flag, 0) }, { FLDATA (FBF, mp_flag_buffer, 0) }, { ORDATA (FR, mp_fence, 15) }, { ORDATA (VR, mp_VR, 16) }, { FLDATA (EVR, mp_evrff, 0) }, { FLDATA (MEV, mp_mevff, 0) }, { FLDATA (ENABLED, mp_enabled, 0), REG_HRO }, { FLDATA (REENABLE, mp_reenable, 0), REG_HRO }, { ORDATA (PLBOUND, jsb_bound, 16), REG_HRO }, { NULL } }; /* Modifier list */ static MTAB mp_mod [] = { /* Mask Value Match Value Print String Match String Validation Display Descriptor */ /* ------------- ------------ --------------- ------------ ----------- ------- ---------- */ { UNIT_MP_JSB, UNIT_MP_JSB, "JSB (W5) out", "JSBOUT", &mp_set_jsb, NULL, NULL }, { UNIT_MP_JSB, 0, "JSB (W5) in", "JSBIN", &mp_set_jsb, NULL, NULL }, { UNIT_MP_INT, UNIT_MP_INT, "INT (W6) out", "INTOUT", NULL, NULL, NULL }, { UNIT_MP_INT, 0, "INT (W6) in", "INTIN", NULL, NULL, NULL }, { UNIT_MP_SEL1, UNIT_MP_SEL1, "SEL1 (W7) out", "SEL1OUT", NULL, NULL, NULL }, { UNIT_MP_SEL1, 0, "SEL1 (W7) in", "SEL1IN", NULL, NULL, NULL }, { 0 } }; /* Trace list */ static DEBTAB mp_deb [] = { { "IOBUS", TRACE_IOBUS }, /* trace I/O bus signals and data words received and returned */ { NULL, 0 } }; /* Device descriptor */ DEVICE mp_dev = { "MP", /* device name */ mp_unit, /* unit array */ mp_reg, /* register array */ mp_mod, /* modifier array */ 1, /* number of units */ 8, /* address radix */ 1, /* address width */ 1, /* address increment */ 8, /* data radix */ 16, /* data width */ NULL, /* examine routine */ NULL, /* deposit routine */ &mp_reset, /* reset routine */ NULL, /* boot routine */ NULL, /* attach routine */ NULL, /* detach routine */ &mp_dib, /* device information block pointer */ DEV_DISABLE | DEV_DIS | DEV_DEBUG, /* device flags */ 0, /* debug control flags */ mp_deb, /* debug flag name table */ NULL, /* memory size change routine */ NULL /* logical device name */ }; /* Main memory global utility routines */ /* Initialize main memory. This routine allocates and zeros the array of MEMORY_WORDs that represent the main memory of the CPU. It also obtains and saves a pointer to the DIB of the Time Base Generator device for RTE idle detection. On entry, the "memory_size" parameter will be the number of words to allocate and will represent the largest possible memory supported by the most expansive CPU model. If memory allocation failed, or the TBG device was not found, the routine returns an appropriate error. Implementation notes: 1. This routine is called only once during simulator startup. If it returns an error status, then the simulator exits. 2. The TBG device is initially named "CLK" (for backward compatibility). The logical name "TBG" is assigned by the TBG device reset routine, but we are called before that routine is executed, so the logical name does not exist when we are called. */ t_stat mem_initialize (uint32 memory_size) { DEVICE *tbg_dptr; M = (MEMORY_WORD *) calloc (memory_size, /* allocate the maximum amount of memory needed */ sizeof (MEMORY_WORD)); tbg_dptr = find_dev ("CLK"); /* get a pointer to the time-base generator device */ if (tbg_dptr == NULL) /* if the TBG device is not present */ return SCPE_IERR; /* then something is seriously wrong */ else /* otherwise */ tbg_dibptr = (DIB *) tbg_dptr->ctxt; /* set a pointer to the device's DIB */ if (M == NULL) /* if memory allocation failed */ return SCPE_MEM; /* then report the error */ else /* otherwise */ return SCPE_OK; /* report successful allocation */ } /* Read a word from memory. This routine reads and returns a word from memory at the indicated logical address. On entry, "dptr" points to the DEVICE structure of the device requesting access, "classification" is the type of access requested, and "address" is the offset into the 32K logical address space implied by the classification. If memory expansion is enabled, the logical address is mapped into a physical memory location; the map used is determined by the access classification. The current map (user or system), alternate map (the map not currently selected), or an explicit map (system, user, DCPC port A, or DCPC port B) may be requested. Read protection is enabled for current or alternate map access and disabled for the others. If memory expansion is disabled or not present, the logical address directly accesses the first 32K of memory. The Memory Protect (MP) and Memory Expansion Module (MEM) accessories provide a protected mode that guards against improper accesses by user programs. They may be enabled or disabled independently, although protection requires that both be enabled. MEM checks that read protection rules on the target page are compatible with the access desired. If the check fails, and MP is enabled, then the request is aborted. The 1000 family maps memory location 0 to the A-register and location 1 to the B-register. CPU reads of these locations return the A- or B-register values, while DCPC reads access physical memory locations 0 and 1 instead. Implementation notes: 1. A read beyond the limit of physical memory returns 0. This is handled by allocating the maximum memory array and initializing memory beyond the defined limit to zero, so no special handling is needed here. 2. A MEM read protection violation with MP enabled causes an MP abort instead of a normal return from the "map_address" routine.. 3. In hardware, a FTCH micro-order clocks the address on the MBUS into the MP Violation Register if the EVR (Enable Violation Register) flip-flop is set. An MP or MEM violation clears EVR, preserving the address of the violating instruction until the Violation Register is read during abort processing. */ HP_WORD mem_read (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD address) { uint32 index; MEU_MAP_SELECTOR map; HP_WORD protection; MR = address; /* save the logical memory address */ switch (classification) { /* dispatch on the access classification */ case Fetch: if (mp_evrff) /* if the violation register is enabled */ mp_VR = address; /* then update it with the instruction address */ map = meu_current_map; /* use the currently selected map (user or system) */ protection = READ_PROTECTED; /* and enable read protection */ break; case Data: default: /* needed to quiet the compiler's anxiety */ map = meu_current_map; /* use the currently selected map (user or system) */ protection = READ_PROTECTED; /* and enable read protection */ break; case Data_Alternate: map = ALTERNATE_MAP (meu_current_map); /* use the alternate map (user or system) */ protection = READ_PROTECTED; /* and enable read protection */ break; case Data_System: map = System_Map; /* use the system map explicitly */ protection = NO_PROTECTION; /* without protection */ break; case Data_User: map = User_Map; /* use the user map explicitly */ protection = NO_PROTECTION; /* without protection */ break; case DMA_Channel_1: map = Port_A_Map; /* use the DCPC port A map */ protection = NO_PROTECTION; /* without protection */ break; case DMA_Channel_2: map = Port_B_Map; /* use the DCPC port B map */ protection = NO_PROTECTION; /* without protection */ break; } /* all cases are handled */ index = map_address (address, map, protection); /* translate the logical address to a physical address */ if (index > 1 || map >= Port_A_Map) /* if memory is referenced or this is a DCPC transfer */ TR = (HP_WORD) M [index]; /* then return the physical memory value */ else /* otherwise */ TR = ABREG [index]; /* return the selected register value */ tpprintf (dptr, mem_access [classification].debug_flag, DMS_FORMAT " %s%s\n", meu_indicator, meu_page, MR, TR, mem_access [classification].name, mem_access [classification].debug_flag == TRACE_FETCH ? "" : " read"); return TR; /* return the word that was read */ } /* Write a word to memory. This routine writes a word to memory at the indicated logical address. On entry, "dptr" points to the DEVICE structure of the device requesting access, "classification" is the type of access requested, "address" is the offset into the 32K logical address space implied by the classification, and "value" is the value to write. If memory expansion is enabled, the logical address is mapped into a physical memory location; the map used is determined by the access classification. The current map (user or system), alternate map (the map not currently selected), or an explicit map (system, user, DCPC port A, or port B) may be requested. Write protection is enabled for current or alternate map access and disabled for the others. If memory expansion is disabled or not present, the logical address directly accesses the first 32K of memory. The Memory Protect (MP) and Memory Expansion Module (MEM) accessories provide a protected mode that guards against improper accesses by user programs. They may be enabled or disabled independently, although protection requires that both be enabled. MP checks that memory writes do not fall below the Memory Protect Fence Register (MPFR) value, and MEM checks that write protection rules on the target page are compatible with the access desired. If either check fails, and MP is enabled, then the request is aborted (so, to pass, a page must be writable AND the target must be above the MP fence). In addition, a MEM write violation will occur if MP is enabled and the alternate map is selected, regardless of the page protection. The 1000 family maps memory location 0 to the A-register and location 1 to the B-register. CPU writes to these locations store the values into the A or B register, while DCPC writes access physical memory locations 0 and 1 instead. MP uses a lower bound of 2 for memory writes, allowing unrestricted access to the A and B registers. Implementation notes: 1. if memoy expansion is disabled, a write beyond the limit of physical memory is a no-operation. If expansion is enabled, it is a NOP if the page is not write-protected. 2. When the alternate map is enabled, writes are permitted only in the unprotected mode, regardless of page protections or the MP fence setting. This behavior is not mentioned in the MEM documentation, but it is tested by the MEM diagnostic and is evident from the MEM schematic. Referring to Sheet 2 in the ERD, gates U125 and U127 provide this logic: WTV = MPCNDB * MAPON * (WPRO + ALTMAP) The ALTMAP signal is generated by the not-Q output of flip-flop U117, which toggles on control signal -CL3 assertion (generated by the MESP microorder) to select the alternate map. Therefore, a write violation is indicated whenever a memory protect check occurs while the MEM is enabled and either the page is write-protected or the alternate map is selected. The hardware reference manuals that contain descriptions of those DMS instructions that write to the alternate map (e.g., MBI) say, "This instruction will always cause a MEM violation when executed in the protected mode and no bytes [or words] will be transferred." However, they do not state that a write violation will be indicated, nor does the description of the write violation state that this is a potential cause. */ void mem_write (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD address, HP_WORD value) { uint32 index; MEU_MAP_SELECTOR map; HP_WORD protection; MR = address; /* save the logical memory address */ switch (classification) { /* dispatch on the access classification */ case Data: default: /* needed to quiet the compiler's anxiety */ map = meu_current_map; /* use the currently selected map (user or system) */ protection = WRITE_PROTECTED; /* and enable write protection */ break; case Data_Alternate: map = ALTERNATE_MAP (meu_current_map); /* use the alternate map (user or system) */ protection = WRITE_PROTECTED; /* and enable write protection */ if (meu_status & MEST_ENABLED) /* if the MEM is enabled */ dm_violation (MEVI_WRITE); /* then a violation always occurs if in protected mode */ break; case Data_System: map = System_Map; /* use the system map explicitly */ protection = NO_PROTECTION; /* without protection */ break; case Data_User: map = User_Map; /* use the user map explicitly */ protection = NO_PROTECTION; /* without protection */ break; case DMA_Channel_1: map = Port_A_Map; /* use the DCPC port A map */ protection = NO_PROTECTION; /* without protection */ break; case DMA_Channel_2: map = Port_B_Map; /* use the DCPC port B map */ protection = NO_PROTECTION; /* without protection */ break; case Fetch: /* instruction fetches */ return; /* do not cause writes */ } /* all cases are handled */ index = map_address (address, map, protection); /* translate the logical address to a physical address */ if (protection == WRITE_PROTECTED /* if protection is wanted */ && address >= 2 && address < mp_fence) /* and the MP check fails */ mp_violation (); /* then a memory protect violation occurs */ if (index <= 1 && map <= User_Map) /* if the A/B register is referenced in the system or user map */ ABREG [index] = value; /* then write the value to the selected register */ else if (index < mem_end) /* otherwise if the location is within defined memory */ M [index] = (MEMORY_WORD) value; /* then write the value to memory */ TR = value; /* save the value just written */ tpprintf (dptr, mem_access [classification].debug_flag, DMS_FORMAT " %s write\n", meu_indicator, meu_page, MR, TR, mem_access [classification].name); return; } /* Read a byte from memory. This routine reads and returns a byte from memory at the indicated logical address. On entry, "dptr" points to the DEVICE structure of the device requesting access, "classification" is the type of access requested, and "byte_address" is the byte offset into the 32K logical address space implied by the classification. The HP 1000 is a word-oriented machine. To permit byte accesses, a logical byte address is defined as two times the associated word address. The LSB of the byte address designates the byte to access: 0 for the upper byte, and 1 for the lower byte. As all 16 bits are used, byte addresses cannot be indirect. Implementation notes: 1. Word buffering is not used to minimize memory reads, as the HP 1000 microcode does a full word read for each byte accessed. */ uint8 mem_read_byte (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD byte_address) { const HP_WORD word_address = byte_address >> 1; /* the address of the word containing the byte */ HP_WORD word; word = mem_read (dptr, classification, word_address); /* read the addressed word */ if (byte_address & LSB) /* if the byte address is odd */ return LOWER_BYTE (word); /* then return the right-hand byte */ else /* otherwise */ return UPPER_BYTE (word); /* return the left-hand byte */ } /* Write a byte to memory. This routine writes a byte to memory at the indicated logical address. On entry, "dptr" points to the DEVICE structure of the device requesting access, "classification" is the type of access requested, "byte_address" is the byte offset into the 32K logical address space implied by the classification, and "value" is the value to write. The HP 1000 is a word-oriented machine. To permit byte accesses, a logical byte address is defined as two times the associated word address. The LSB of the byte address designates the byte to access: 0 for the upper byte, and 1 for the lower byte. As all 16 bits are used, byte addresses cannot be indirect. Implementation notes: 1. Word buffering is not used to minimize memory writes, as the HP 1000 base-set microcode does a full word write for each byte accessed. (The DMS byte instructions, e.g., MBI, do full-word accesses for each pair of bytes, but that is to minimize the number of map switches.) */ void mem_write_byte (DEVICE *dptr, ACCESS_CLASS classification, HP_WORD byte_address, uint8 value) { const HP_WORD word_address = byte_address >> 1; /* the address of the word containing the byte */ HP_WORD word; word = mem_read (dptr, classification, word_address); /* read the addressed word */ if (byte_address & LSB) /* if the byte address is odd */ word = REPLACE_LOWER (word, value); /* then replace the right-hand byte */ else /* otherwise */ word = REPLACE_UPPER (word, value); /* replace the left-hand byte */ mem_write (dptr, classification, word_address, word); /* write the updated word back */ return; } /* Fast read from memory. This routine reads and returns a word from memory at the indicated logical address using the specified map. Memory protection is not used, and tracing is not available. This routine is used when fast, unchecked access to mapped memory is required. */ HP_WORD mem_fast_read (HP_WORD address, MEU_MAP_SELECTOR map) { if (map == Current_Map) /* if the current map is requested */ map = meu_current_map; /* then use it */ return mem_examine (map_address (address, map, NO_PROTECTION)); /* return the value from the selected map */ } /* Zero a range of memory locations. Main memory locations from a supplied starting address through the end of defined memory are filled with the specified value. This routine is typically called to zero non-existent memory when the main memory size is reduced (so that non-existent locations will read as zero). */ void mem_zero (uint32 starting_address, uint32 fill_count) { memset (M + starting_address, 0, /* zero the words */ fill_count * sizeof (MEMORY_WORD)); /* in the specified memory range */ return; } /* Check for a non-zero value within a memory address range. This routine checks a range of memory locations for the presence of a non-zero value. The starting address of the range is supplied, and the check continues through the end of defined memory. The routine returns TRUE if the memory range was empty (i.e., contained only zero values) and FALSE otherwise. */ t_bool mem_is_empty (uint32 starting_address) { uint32 address; for (address = starting_address; address < mem_size; address++) /* loop through the specified address range */ if (M [address] != 0) /* if this location is non-zero */ return FALSE; /* then indicate that memory is not empty */ return TRUE; /* return TRUE if all locations contain zero values */ } /* Copy a binary loader to or from protected memory. This routine is called to copy a 64-word binary loader from a buffer to memory or vice versa. On entry, "buffer" points at an array of MEMORY_WORDs sufficiently large to hold a 64-word binary loader, "starting_address" is the address in memory corresponding to the loader target, and "mode" is "To_Memory" to copy from the buffer to memory or "From_Memory" to copy from memory to the buffer. If copying from memory, the copied memory area is zeroed before returning (memory is zeroed in preparation to protecting the reserved loader area). */ void mem_copy_loader (MEMORY_WORD *buffer, uint32 starting_address, COPY_DIRECTION mode) { if (mode == To_Memory) /* if copying into memory */ memcpy (M + starting_address, buffer, /* then transfer the loader */ IBL_SIZE * sizeof (MEMORY_WORD)); /* from the buffer to memory */ else { /* otherwise */ memcpy (buffer, M + starting_address, /* transfer the loader */ IBL_SIZE * sizeof (MEMORY_WORD)); /* from memory to the buffer */ mem_zero (starting_address, IBL_SIZE); /* zero the vacated memory area */ } return; } /* Determine if the CPU is idle. This routine determines whether the CPU is executing an operating system idle loop. It is called when a JMP or JMP,I instruction is executed with CPU idling enabled and no interrupt pending. The 21xx/1000 CPUs have no "wait for interrupt" instruction. Idling in HP operating systems consists of sitting in "idle loops" that end with JMP instructions. We test for certain known patterns when a JMP instruction is executed to decide if the simulator should idle. If execution is within a recognized idle loop, the routine returns TRUE; in response, the simulator will call the "sim_idle" routine to suspend execution until the next event service is due. If the CPU is not executing an idle loop, the routine returns FALSE to continue normal execution. On entry, MR contains the address of the jump target, and "err_PR" contains the address of the jump instruction. The difference gives the jump displacement. The recognized idle patterns are operating-system-specific, as follows: for RTE-6/VM: - ISZ / JMP *-1 - mp_fence = 0 - XEQT (address 1717B) = 0 - MEU on with system map enabled - RTE verification: TBG (address 1674B) = TBG select code for RTE though RTE-IVB: - JMP * - mp_fence = 0 - XEQT (address 1717B) = 0 - MEU on with user map enabled (RTE-III through RTE-IVB only) - RTE verification: TBG (address 1674B) = TBG select code for DOS through DOS-III: - STF 0 / CCA / CCB / JMP *-3 - DOS verification: A = B = -1, address 40B = -64, address 67B = +64 Note that in DOS, the TBG is set to 100 milliseconds vs. 10 milliseconds for RTE. */ t_bool mem_is_idle_loop (void) { const int32 displacement = (int32) MR - (int32) err_PR; /* the jump displacement */ if ((displacement == 0 /* if the jump target is * (RTE through RTE-IVB) */ || displacement == -1 && (M [MR] & IR_MRG) == IR_ISZ) /* or the target is *-1 (RTE-6/VM) and *-1 is ISZ */ && mp_fence == 0 /* and the MP fence is zero */ && M [xeqt] == 0 /* and no program is executing */ && M [tbg] == tbg_dibptr->select_code /* and the TBG select code is correct */ || displacement == -3 /* or the jump target is *-3 (DOS through DOS-III) */ && M [MR] == IR_STF /* and *-3 is STF 0 */ && AR == 0177777u /* and the A and B registers */ && BR == 0177777u /* are both set to -1 */ && M [m64] == 0177700u /* and the -64 and +64 base-page constants */ && M [p64] == 0000100u) /* are set as expected */ return TRUE; /* then the system is executing an idle lop */ else /* otherwise */ return FALSE; /* the system is not executing an idle loop */ } /* Trace the working and MP/MEM registers. This routine is called when CPU register tracing is enabled. It reports the content of the working registers (S. A, B, X, Y, E, and O), memory protection status (on or off), interrupt system status (on or off), and the current MEU base page fence value. If the MP or MEM working registers changed since the last trace report, an additional line is printed to report the memory protect fence and violation registers and the memory expansion status and violation registers. Implementation notes: 1. The "is_1000" flag is used to include or omit, based on the CPU model, the X and Y registers from the working register trace and the MEVR and MESR from the memory protection trace. */ void mem_trace_registers (FLIP_FLOP interrupt_system) { hp_trace (&cpu_dev, TRACE_REG, /* output the working registers */ register_formats [is_1000], /* using a format appropriate for the CPU model */ mp_value [mp_control], meu_status & MEST_FENCE_MASK, SR, AR, BR, XR, YR); fputs (register_values [E << 2 | O << 1 | interrupt_system], sim_deb); /* output E, O, and interrupt system */ fputc ('\n', sim_deb); if (mp_mem_changed) { /* if the MP/MEM registers have been altered */ hp_trace (&cpu_dev, TRACE_REG, /* then output the register values */ mp_mem_formats [is_1000], /* using a format appropriate for the CPU model */ mp_value [mp_control], mp_fence, mp_VR, meu_status, meu_violation); mp_mem_changed = FALSE; /* clear the MP/MEM registers changed flag */ } } /* Examine a physical memory address. This routine reads and returns a word from memory at the indicated physical address. If the address lies outside of allocated memory, a zero value is returned. There are no protections or error indications. */ HP_WORD mem_examine (uint32 address) { if (address <= 1 && !(sim_switches & SIM_SW_REST)) /* if the address is 0 or 1 and not restoring memory */ return ABREG [address]; /* then return the A or B register value */ else if (address <= PA_MAX) /* otherwise if the address is within allocated memory */ return (HP_WORD) M [address]; /* then return the memory value */ else /* otherwise the access is outside of memory */ return 0; /* which reads as zero */ } /* Deposit into a physical memory address. This routine writes a word into memory at the indicated physical address. If the address lies outside of defined memory, the write is ignored. There are no protections or error indications. */ void mem_deposit (uint32 address, HP_WORD value) { if (address <= 1 && !(sim_switches & SIM_SW_REST)) /* if the address is 0 or 1 and not restoring memory */ ABREG [address] = value & DV_MASK; /* then store into the A or B register */ else if (address < mem_end) /* otherwise if the address is within defined memory */ M [address] = (MEMORY_WORD) value & DV_MASK; /* then store the value */ return; } /* Memory Expansion Unit global utility routines */ /* Configure the Memory Expansion Module. This routine enables or disables the MEM, depending on the "configuration" parameter. If the MEM is being enabled, the "device disabled" flag is cleared. Otherwise, the flag is set, and mapping is disabled so that address translation will not occur. The routine is called when the DMS instruction set is enabled or disabled. The MEM device state tracks the instruction state and cannot be set independently, i.e., with a SET MEM DISABLED command. */ void meu_configure (MEU_STATE configuration) { if (configuration == ME_Enabled) /* if DMS instructions are enabled */ meu_dev.flags &= ~DEV_DIS; /* then enable the MEM device */ else { /* otherwise */ meu_dev.flags |= DEV_DIS; /* disable the MEM */ meu_set_state (ME_Disabled, System_Map); /* and disable mapping */ } return; } /* Read a map register. This routine is called to read one map register from the specified map. The map index may be from 0-31 to read from a specific map (System_Map, User_Map, etc.) or may be from 0-127 to read a linear sequence of maps (Linear_Map). The map content (the protection bits and a physical page number corresponding to the logical page number specified by the index) is returned. */ HP_WORD meu_read_map (MEU_MAP_SELECTOR map, uint32 index) { if (map == Linear_Map) /* if linear access is specified */ return meu_maps [index / REG_COUNT & MAP_MASK] /* then use the upper index bits for the map */ [index % REG_COUNT]; /* and the lower index bits for the register */ else /* otherwise */ return meu_maps [map] [index]; /* read from the specified map and register */ } /* Write a map register. This routine is called to write a value into one map register of the specified map. The map index may be from 0-31 to write to a specific map (System_Map, User_Map, etc.) or may be from 0-127 to write a linear sequence of maps (Linear_Map). The map content (the protection bits and a physical page number corresponding to the logical page number specified by the index) is stored in the indicated register. */ void meu_write_map (MEU_MAP_SELECTOR map, uint32 index, uint32 value) { if (map == Linear_Map) /* if linear access is specified */ meu_maps [index / REG_COUNT & MAP_MASK] /* then use the upper index bits for the map */ [index % REG_COUNT] = value & ~MAP_RESERVED; /* and the lower index bits for the register */ else /* otherwise */ meu_maps [map] [index] = value & ~MAP_RESERVED; /* write to the specified map and register */ return; } /* Set the MEM fence register. This routine sets a new value into the MEM base-page fence register. The value must have the "portion mapped" flag in bit 10 and the fence address is bits 9-0. No error checking is performed. */ void meu_set_fence (HP_WORD new_fence) { meu_status = meu_status & ~(MEST_BELOW | MEST_FENCE_MASK) /* mask off the old mapping and address */ | new_fence & (MEST_BELOW | MEST_FENCE_MASK); /* and merge in the new values */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ return; } /* Set the Memory Expansion Unit state. This routine is called to enable or disable the MEM and to set the current map. */ void meu_set_state (MEU_STATE operation, MEU_MAP_SELECTOR map) { if (operation == ME_Enabled) /* if the MEM is being enabled */ meu_status |= MEST_ENABLED; /* then set the MEM enabled status bit */ else { /* otherwise disable the MEM */ meu_status &= ~MEST_ENABLED; /* by clearing the MEM enabled status bit */ meu_bus_enabled = FALSE; /* and disabling the MEM expansion bus */ } meu_current_map = map; /* set the current map in either case */ mp_mem_changed = TRUE; /* and set the MP/MEM registers changed flag */ return; } /* Update the MEM violation register. This routine is called to update the MEM violation register. This is done whenever the value in the register might be examined. In hardware, the MEM violation register (MEVR) is clocked on every memory read, every JMP or memory write (actually, every use of the MPCK micro-order) above the lower bound of protected memory, and every execution of a privileged DMS instruction. The register is not clocked when MP is disabled by an MP or MEM error (i.e., when MEVFF sets or CTL5FF clears), in order to capture the state of the MEM. In other words, the MEVR continually tracks the memory map register accessed plus the MEM state (MEBEN, MAPON, and USR) until a violation occurs, and then it's "frozen." Under simulation, we do not have to update the MEVR on every memory access, because the visible state is only available via a programmed RVA/B instruction or via the SCP interface. Therefore, it is sufficient if the register is updated: - at a MEM violation (when freezing) - at an MP violation (when freezing) - during RVA/B execution (if not frozen) - before returning to SCP after a simulator stop (if not frozen) The routine returns the updated content of the violation register. */ HP_WORD meu_update_violation (void) { if (mp_control && mp_mevff == CLEAR) { /* if the violation register is not frozen */ meu_violation = PAGE (MR); /* then set the last map addressed */ if (meu_status & MEST_ENABLED) /* if the MEM is currently enabled */ meu_violation |= MEVI_MEM_ENABLED; /* then add the status bit */ if (meu_current_map == User_Map) /* if the user map is currently enabled */ meu_violation |= MEVI_USER_MAP; /* then add the status bit */ if (meu_bus_enabled) /* if the last memory address was mapped */ meu_violation |= MEVI_BUS_ENABLED; /* then add the "ME bus is enabled" bit */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ } return meu_violation; /* return the violation register content */ } /* Update the MEM status register. This routine is called to update the MEM status register. This is done whenever the value in the register might be examined. In hardware, the MEM status register (MESR) is not a physical register but rather a set of tristate drivers that enable the base-page fence register, the current state of the MEM (disabled or enabled, system or user map), and the MEM state at last interrupt onto the CPU's S-bus. Under simulation, we do not have to update the MESR each time the current map changes, because the visible state is only available via programmed RSA/B and SSM instructions, via an RTE OS trap cell instruction (where it is used to save the MEM state), or via the SCP interface. Therefore, it is sufficient if the register is updated: - during RSA/B or SSM or RTE OS trap cell instruction execution - before returning to SCP after a simulator stop The routine returns the updated content of the status register. */ HP_WORD meu_update_status (void) { meu_status &= ~MEST_DYNAMIC; /* clear the current MEM state */ if (meu_current_map == User_Map) /* if the user map is enabled */ meu_status |= MEST_USER_MAP; /* then set the currently enabled bit */ if (mp_control) /* if MP is enabled */ meu_status |= MEST_PROTECTED; /* then set the protected mode bit */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ return meu_status; /* return the status register value */ } /* Assert an Interrupt Acknowledge signal to the MEM. This routine asserts the IAK signal to the Memory Expansion Module. It is called when the CPU acknowledges an interrupt. In response, the MEM saves its current state and switches to the system map for interrupt processing. In addition, if the CPU is tracing instructions, the routine calls "map_address" to set the current map indicator and the page number of the next instruction to execute. This will be used by the CPU to print the interrupt location. */ void meu_assert_IAK (void) { meu_status &= ~MEST_DYNAMIC_IAK; /* clear the MEM interrupt state */ if (meu_status & MEST_ENABLED) /* if the MEM is enabled */ meu_status |= MEST_ENBL_INT; /* then add the enabled-at-interrupt bit */ if (meu_current_map == User_Map) /* if the user map is enabled */ meu_status |= MEST_UMAP_INT; /* then add the user-map-at-interrupt bit */ if (TRACING (cpu_dev, TRACE_INSTR)) /* if instruction tracing is active */ map_address (PR, meu_current_map, NO_PROTECTION); /* then set the MEM page and indicator */ meu_current_map = System_Map; /* switch to the system map for the interrupt */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ return; } /* Generate a MEM privilege violation. This routine conditionally generates a dynamic mapping violation. If the condition is "Always", then a privilege violation is generated. If the condition is "If_User_Map", then a violation occurs if the user map is the current map; otherwise, no violation occurs. Implmentation notes: 1. If the MEM is in the protected mode, i.e., memory protect is on, a DM violation will cause a microcode abort, and this routine will not return. */ void meu_privileged (MEU_CONDITION condition) { if (condition == Always || meu_current_map == User_Map) dm_violation (MEVI_PRIVILEGE); return; } /* Get the current MEM breakpoint type. This routine returns a command line switch value representing the breakpoint type that corresponds to the current MEM configuration. It is used to get the current default breakpoint type, as follows: MEM State Current Map Breakpoint Type --------- ----------- --------------- disabled -- N enabled System S enabled User U The "is_iak" parameter is used to qualify the "U" type. If the user map is currently enabled but an interupt acknowledgement is pending, then the returned type is "S", as the IAK will be handled in the system map. */ uint32 meu_breakpoint_type (t_bool is_iak) { if (meu_status & MEST_ENABLED) /* if MEM is currently enabled */ if (meu_current_map == User_Map && !is_iak) /* then if the user map is currently enabled */ return SWMASK ('U'); /* then return the user breakpoint switch */ else /* otherwise */ return SWMASK ('S'); /* return the system breakpoint switch */ else /* otherwise MEM is disabled */ return SWMASK ('N'); /* so return the non-MEM breakpoint switch */ } /* Translate a logical address for console access. This routine translates a logical address interpreted in the context of the translation map implied by the specified switch to a physical address. It is called to map addresses when the user is examining or depositing memory. It is also called to restore a saved configuration, although mapping is not used for restoration. All memory protection checks are off for console access. Command line switches modify the interpretation of logical addresses as follows: Switch Meaning ------ -------------------------------------------------- -N Use the address directly with no mapping -S If memory expansion is enabled, use the system map -U If memory expansion is enabled, use the user map -P If memory expansion is enabled, use the port A map -Q If memory expansion is enabled, use the port B map If no switch is specified, then the address is interpreted using the current map if memory expansion is enabled; otherwise, the address is not mapped. If the current or specified map is used, then the address must lie within the 32K logical address space; if not, then an address larger than the current memory size is returned to indicate that a translation error occurred. */ uint32 meu_map_address (HP_WORD logical, int32 switches) { MEU_MAP_SELECTOR map; if (switches & (SWMASK ('N') | SIM_SW_REST)) /* if no mapping is requested */ return logical; /* then the address is already a physical address */ else if ((meu_status & MEST_ENABLED) == 0 /* otherwise if the MEM is disabled */ && switches & ALL_MAPMODES) /* but a mapping mode was given */ return D32_UMAX; /* then the command is not allowed */ else if ((meu_status & MEST_ENABLED || switches & ALL_MAPMODES) /* otherwise if mapping is enabled or requested */ && logical > LA_MAX) /* and the address is not a logical address */ return mem_size; /* then report a memory overflow */ else if (switches & SWMASK ('S')) /* otherwise if the -S switch is specified */ map = System_Map; /* then use the system map */ else if (switches & SWMASK ('U')) /* otherwise if the -U switch is specified */ map = User_Map; /* then use the user map */ else if (switches & SWMASK ('P')) /* otherwise if the -P switch is specified */ map = Port_A_Map; /* then use the DCPC port A map */ else if (switches & SWMASK ('Q')) /* otherwise if the -Q switch is specified */ map = Port_B_Map; /* then use the DCPC port B map */ else /* otherwise */ map = meu_current_map; /* use the current map (system or user) */ return map_address (logical, map, NO_PROTECTION); /* translate the address without protection */ } /* Memory Expansion Unit local SCP support routines */ /* Memory Expansion Unit reset. The MEM processes POPIO but is not addressed by a select code and so does not have an I/O interface. Therefore, we handle POPIO here. */ static t_stat meu_reset (DEVICE *dptr) { meu_current_map = System_Map; /* enable the system map */ meu_status = 0; /* disable MEM and clear the status register */ meu_violation = 0; /* clear the violation register */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ return SCPE_OK; } /* Memory Expansion Unit local utility routines */ /* Process a MEM violation. A MEM violation will report the cause in the violation register. This occurs even if the MEM is not in the protected mode (i.e., MP is not enabled). If MP is enabled, an MP abort is taken with the MEV flip-flop set. Otherwise, we return to the caller. */ static void dm_violation (HP_WORD violation) { meu_violation = violation | meu_update_violation (); /* set the cause in the violation register */ if (mp_control) { /* if memory protect is on */ mp_mem_changed = TRUE; /* then set the MP/MEM registers changed flag */ mp_mevff = SET; /* record a memory expansion violation */ mp_violation (); /* and a memory protect violation */ } return; } /* Determine whether an address is mapped. This routine determines whether a logical address is mapped to a physical address or represents a physical address itself. It corresponds to the hardware MEBEN (Memory Expansion Bus Enable) signal and indicates that a memory access is not in the unmapped portion of the base page. The routine is called only if the MEM is enabled and returns TRUE if the address is mapped or FALSE if it is unmapped. Before returning, "meu_bus_enabled" is set to reflect the mapping state. */ static t_bool is_mapped (HP_WORD address) { HP_WORD dms_fence; if (address <= 1) /* if the reference is to the A or B register */ meu_bus_enabled = FALSE; /* then the location is not mapped */ else if (address <= LWA_BASE_PAGE) { /* otherwise if the address is on the base page */ dms_fence = meu_status & MEST_FENCE_MASK; /* then get the base-page fence value */ if (meu_status & MEST_BELOW) /* if the lower portion is mapped */ meu_bus_enabled = (address < dms_fence); /* then mapping occurs if the address is below the fence */ else /* otherwise the upper portion is mapped */ meu_bus_enabled = (address >= dms_fence); /* so mapping occurs if the address is at or above the fence */ } else /* otherwise the address is not on page 0 */ meu_bus_enabled = TRUE; /* so it is always mapped */ return meu_bus_enabled; /* return the mapping state */ } /* Map a logical address to a physical address. This routine translates logical to physical addresses. The logical address, desired map, and desired access protection are supplied. If the access is legal, the mapped physical address is returned; if it is not, then a MEM violation occurs. The current map may be specified by passing "meu_current_map" as the "map" parameter, or a specific map may be used. Normally, read and write accesses pass READ_PROTECTED or WRITE_PROTECTED, respectively, as the "protection" parameter to request access checking. For DCPC accesses, NO_PROTECTION must be passed to inhibit access checks. This routine checks for read, write, and base-page violations and will call "dm_violation" as appropriate. The latter routine will abort if MP is enabled, or will return if protection is off. */ static uint32 map_address (HP_WORD address, MEU_MAP_SELECTOR map, HP_WORD protection) { uint32 map_register; if (meu_status & MEST_ENABLED) { /* if the Memory Expansion Unit is enabled */ meu_indicator = map_indicator [map]; /* then set the map indicator to the applied map */ if (address > LWA_BASE_PAGE /* if the address is not on the base page */ || map >= Port_A_Map /* or this is a DCPC transfer */ || is_mapped (address)) { /* or it is to the mapped portion of the base page */ map_register = meu_maps [map] [PAGE (address)]; /* then get the map register for the logical page */ meu_page = MAP_PAGE (map_register); /* save the physical page number */ if (map_register & protection) /* if the desired access is not allowed */ dm_violation (protection); /* then a read or write protection violation occurs */ return TO_PA (meu_page, address); /* form the physical address from the mapped page and offset */ } else { /* otherwise the address is unmapped */ meu_page = 0; /* so the physical page is page 0 */ if (address > 1 && protection == WRITE_PROTECTED) /* a write to the unmapped part of the base page */ dm_violation (MEVI_BASE_PAGE); /* causes a base-page violation if protection is enabled */ return address; /* the address is already physical */ } } else { /* otherwise the MEU is disabled */ meu_page = PAGE (address); /* so the physical page is the logical page */ meu_indicator = '-'; /* and no mapping occurs */ return address; /* the physical address is the logical address */ } } /* Memory Protect I/O interface routine */ /* Memory Protect/Parity Error interface (select code 05). I/O operations directed to select code 5 manipulate the Memory Protect accessory. They also affect main memory parity error and memory expansion violation reporting. STC turns on memory protect, which is turned off only by an MP violation or a POPIO. CLC does nothing. STF and CLF turn parity error interrupts on and off. SFS skips if a MEM violation occurred, while SFC skips if an MP violation occurred. IOI reads the MP violation register; bit 15 of the register is 1 for a parity error and 0 for an MP error. IOO outputs the address of the start of unprotected memory to the MP fence. PRL and IRQ are a function of the MP flag flip-flop only, not the flag and control flip-flops as is usual. IAK is asserted when any interrupt is acknowledged by the CPU. Normally, an interface qualifies IAK with its own IRQ to ensure that it responds only to an acknowledgement of its own request. The MP card does this to reset its flag buffer and flag flip-flops, and to reset the parity error indication. However, it also responds to an unqualified IAK (i.e., for any interface) by clearing the MPV flip-flop, clearing the indirect counter, clearing the control flip-flop, and setting the INTPT flip-flop. The hardware INTPT flip-flop indicates an occurrence of an interrupt. If the trap cell of the interrupting device contains an I/O instruction that is not a HLT, action equivalent to STC 05 is taken, i.e., the interface sets the control and EVR (Enable Violation Register) flip-flops and clears the MEV (Memory Expansion Violation) and PARERR (Parity Error) flip-flops. In simulation, this is handled during IAK processing by setting "mp_enabled" to the state of the MP control flip-flop and scheduling the MP event service routine to enter after the next instruction. If the next instruction, which is the trap cell instruction, is an I/O instruction, "cpu_iog" will call "mp_check_io" as part of its processing. If that routine is called for a non-HLT instruction, it sets "mp_reenable" to the value saved in "mp_enabled", i.e., "mp_reenable" will be SET if MP was enabled when the interrupt occurred (it's initialized to CLEAR). When the service routine is entered after the trap instruction executes, it sets "mp_control" to the value of "mp_reenable", which reenables MP if MP was on. The effect of all of this is to turn MP off when an interrupt occurs but then to reenable it if the interrupt trap cell contained a non-HLT I/O instruction. For example, consider a program executing with MP on and an interrupt from an interface whose trap cell contains a CLF instruction. When the interrupt occurs, MP is turned off, the CLF is executed, MP is turned on, and the program continues. If the trap cell contained a HLT, MP would be turned off, and then the CPU would halt. If the trap cell contained a JSB, MP would be turned off and would remain off while the interrupt subroutine executes. Implementation notes: 1. Because the MP card uses IAK unqualified, this routine is called whenever any interrupt occurs. It is also called when the MP card itself is interrupting. The latter condition is detected by the MP flag flip-flop being set. As MP has higher priority than all devices except power fail, if the flag is set, the IAK must be for the MP card. 2. The MEV flip-flop records memory expansion violations. It is set when a MEM violation is encountered and can be tested via SFC/SFS. 3. The Parity Error logic is not currently implemented. */ static SIGNALS_VALUE mp_interface (const DIB *dibptr, INBOUND_SET inbound_signals, HP_WORD inbound_value) { INBOUND_SIGNAL signal; INBOUND_SET working_set = inbound_signals; SIGNALS_VALUE outbound = { ioNONE, 0 }; t_bool irq_enabled = FALSE; while (working_set) { /* while signals remain */ signal = IONEXTSIG (working_set); /* isolate the next signal */ switch (signal) { /* dispatch the I/O signal */ case ioCLF: /* Clear Flag flip-flop */ break; /* turn parity error interrupts off */ case ioSTF: /* Set Flag flip-flop */ break; /* turn parity error interrupts on */ case ioENF: /* Enable Flag */ if (mp_flag_buffer == SET) /* if the flag buffer flip-flop is set */ if (inbound_signals & ioIEN) { /* then if the interrupt system is on */ mp_flag = SET; /* then set the flag flip-flop */ mp_evrff = CLEAR; /* and inhibit violation register updates */ } else /* otherwise interrupts are off */ mp_flag_buffer = CLEAR; /* and the flag buffer does not set if IEN5 is denied */ break; case ioSFC: /* Skip if Flag is Clear */ if (mp_mevff == CLEAR) /* if this is a memory protect violation */ outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */ break; case ioSFS: /* Skip if Flag is Set */ if (mp_mevff == SET) /* if this is a memory expansion violation */ outbound.signals |= ioSKF; /* then assert the Skip on Flag signal */ break; case ioIOI: /* I/O Data Input */ outbound.value = mp_VR; /* return the MP violation register */ break; case ioIOO: /* I/O Data Output */ mp_fence = inbound_value & LA_MASK; /* store the address in the MP fence register */ if (cpu_configuration & CPU_2100) /* the 2100 IOP instructions */ SPR = mp_fence; /* use the MP fence as a stack pointer */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ break; case ioPOPIO: /* Power-On Preset to I/O */ mp_control = CLEAR; /* clear the control flip-flop */ mp_flag_buffer = CLEAR; /* and the flag buffer flip-flop */ mp_flag = CLEAR; /* and the flag flip-flop */ mp_mevff = CLEAR; /* clear the Memory Expansion Violation flip-flop */ mp_evrff = SET; /* and set the Enable Violation Fegister flip-flop */ mp_reenable = CLEAR; /* clear the MP reenable */ mp_enabled = CLEAR; /* and MP currently enabled flip-flops */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ break; case ioSTC: /* Set Control flip-flop */ mp_control = SET; /* set the control flip-flop to turn on MP */ mp_mevff = CLEAR; /* clear the Memory Expansion Violation flip-flop */ mp_evrff = SET; /* and set the Enable Violation Register flip-flop */ break; case ioSIR: /* Set Interrupt Request */ if (mp_flag) /* if the flag flip-flop is set */ outbound.signals |= cnIRQ | cnVALID; /* then deny PRL and conditionally assert IRQ */ else /* otherwise */ outbound.signals |= cnPRL | cnVALID; /* conditionally assert PRL */ break; case ioIAK: /* Interrupt Acknowledge */ if (mp_flag) { /* if the MP itself is interrupting */ mp_flag_buffer = CLEAR; /* then clear the flag buffer */ mp_flag = CLEAR; /* and flag flip-flops */ } mp_enabled = mp_control; /* set the MP interrupt flip-flop if MP was on */ mp_control = CLEAR; /* and then turn memory protection off */ sim_activate (mp_unit, mp_unit [0].wait); /* schedule a status check for the next instruction */ break; case ioIEN: /* Interrupt Enable */ irq_enabled = TRUE; /* permit IRQ to be asserted */ break; case ioPRH: /* Priority High */ if (irq_enabled && outbound.signals & cnIRQ) /* if IRQ is enabled and conditionally asserted */ outbound.signals |= ioIRQ | ioFLG; /* then assert IRQ and FLG */ if (!irq_enabled || outbound.signals & cnPRL) /* if IRQ is disabled or PRL is conditionally asserted */ outbound.signals |= ioPRL; /* then assert it unconditionally */ break; case ioCRS: /* not used by this interface */ case ioCLC: /* not used by this interface */ case ioEDT: /* not used by this interface */ case ioPON: /* not used by this interface */ break; } IOCLEARSIG (working_set, signal); /* remove the current signal from the set */ } /* and continue until all signals are processed */ return outbound; /* return the outbound signals and value */ } /* Memory Protect global utility routines */ /* Initialize memory protect. This routine is called from the instruction execution prelude to set up the internal state of the memory protect accessory. It returns the state of the MP device (enabled or disabled) to avoid having to make the DEVICE structure global. */ t_bool mp_initialize (void) { is_1000 = (cpu_configuration & CPU_1000) != 0; /* set the CPU model index */ mp_mem_changed = TRUE; /* request an initial MP/MEM trace */ return (mp_dev.flags & DEV_DIS) == 0; /* return TRUE if MP is enabled and FALSE if not */ } /* Configure the Memory Protect accessory. This routine enables or disables MP, depending on the "is_enabled" parameter, and makes the MP configurable or non-configurable, depending on the "is_optional" parameter. It adds or removes the DEV_DIS device flag to disable or enable the device, and adds or removes the DEV_DISABLE device flag to allow or deny the use of SET MP ENABLED/DISABLED SCP commands to change the device state. */ void mp_configure (t_bool is_enabled, t_bool is_optional) { if (is_enabled) /* if MP is to be enabled */ mp_dev.flags &= ~DEV_DIS; /* then remove the "disabled" flag */ else /* otherwise */ mp_dev.flags |= DEV_DIS; /* add the flag to disable the device */ if (is_optional) /* if MP is to be made configurable */ mp_dev.flags |= DEV_DISABLE; /* then add the "can be disabled" flag */ else /* otherwise */ mp_dev.flags &= ~DEV_DISABLE; /* make the current setting unalterable */ return; } /* Check a jump for memory protect or memory expansion violations. This routine checks a jump target address for protection violations. On entry, "address" is the logical address of the jump target, and "lower_bound" is the lowest protected memory address. If a violation occurs, the routine does not return; instead, a microcode abort is taken. Program execution jumps are a special case of write validation. The target address is treated as a write, even when no physical write takes place (e.g., when executing a JMP instead of a JSB), so jumping to a write-protected page causes a MEM violation. In addition, a MEM violation occurs if the jump is to the unmapped portion of the base page. Finally, jumping to a location under the memory-protect fence causes an MP violation. Because the MP and MEM hardware works in parallel, all three violations may exist concurrently. For example, a JMP to the unmapped portion of the base page that is write protected and under the MP fence will indicate a base-page, a write, and an MP violation, whereas a JMP to the mapped portion will indicate a write and an MP violation (BPV is inhibited by the MEBEN signal). If MEM and MP violations occur concurrently, the MEM violation takes precedence, as the SFS and SFC instructions test the MEV flip-flop. The lower bound of protected memory must be either 0 or 2. All violations are qualified by the MPCND signal, which responds to the lower bound. Therefore, if the lower bound is 2, and if the part below the base-page fence is unmapped, or if the base page is write-protected, then a MEM violation will occur only if the access is not to locations 0 or 1. The instruction set firmware uses a lower bound of 0 for JMP, JLY, and JPY (and for JSB with W5 out), and of 2 for DJP, SJP, UJP, JRS, and .GOTO (and JSB with W5 in). Finally, all violations are inhibited if MP is off (i.e., the MP control flip-flop is clear), and MEM violations are inhibited if the MEM is disabled. */ void mp_check_jmp (HP_WORD address, uint32 lower_bound) { const uint32 lp = PAGE (address); /* the logical page number */ HP_WORD violation = 0; /* the MEM violation conditions */ if (mp_control) { /* if memory protect is enabled */ if (meu_status & MEST_ENABLED) { /* then if the MEM is enabled */ if (meu_maps [meu_current_map] [lp] & WRITE_PROTECTED) /* then if the page is write protected */ violation = MEVI_WRITE; /* then a write violation occurs */ if (address >= lower_bound && ! is_mapped (address)) /* if the unmapped base page is the target */ violation |= MEVI_BASE_PAGE; /* then a base page violation occurs */ if (violation) /* if a violation occurred */ dm_violation (violation); /* then assert a MEM violation */ } if (address >= lower_bound && address < mp_fence) /* if the jump is under the memory protect fence */ mp_violation (); /* then a memory protect violation occurs */ } return; } /* Check a jump-to-subroutine for memory protect or memory expansion violations. This routine checks a jump-to-subroutine target address for protection violations. On entry, "address" is the logical address of the jump target. If a violation occurs, the routine does not return; instead, a microcode abort is taken. The protected lower bound address for the JSB instruction depends on the W5 jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs to the A and B registers. If W5 is out, then the lower bound is 0, just as with JMP. */ void mp_check_jsb (HP_WORD address) { mp_check_jmp (address, jsb_bound); /* check the jump target with the selectex bound */ return; } /* Check an I/O operation for memory protect violations. This routine is called by the IOG instruction executor to verify that an I/O instruction is allowed under the current protection settings. On entry, "select_code" is set to the select code addressed by the instruction, and "micro_op" is the IOG operation to be executed. The routine returns if the operation is allowed. Otherwise, an MP abort is performed. If MP is off, then all I/O instructions are allowed. MP will be off during execution of an IOG instruction in an interrupt trap cell; in this case, MP will be reenabled if the instruction is not a HLT and MP was enabled prior to the interrupt. If MP is on, then HLT instructions are illegal and will cause a memory protect violation. If jumper W7 (SEL1) is in, then all other I/O instructions are legal; if W7 is out, then only I/O instructions that address select code 1 are legal, and I/O to other select codes will cause a violation. */ void mp_check_io (uint32 select_code, IO_GROUP_OP micro_op) { if (mp_control == CLEAR) { /* if memory protect is off */ if (micro_op != iog_HLT && micro_op != iog_HLT_C) /* then if the instruction is not a HLT */ mp_reenable = mp_enabled; /* then set up to reenable if servicing an interrupt */ } else if (micro_op == iog_HLT || micro_op == iog_HLT_C /* otherwise if checking a HLT instruction */ || select_code != OVF && (mp_unit [0].flags & UNIT_MP_SEL1)) /* or SC is not 1 and the SEL1 jumper is out */ mp_violation (); /* then a memory protect violation occurs */ return; } /* Process a memory protect violation. If memory protect is on, this routine updates the MEM violation register (if this is an MP and not a MEM violation), sets the MP flag buffer and flag flip-flops (if interupts are enabled), and performs a microcode abort. The latter does a "longjmp" back to the microcode abort handler just prior to the CPU instruction execution loop. If memory protect is off, MP violations are ignored. Implementation notes: 1. The "cpu_microcode_abort" routine is called both for MP and MEM violations. The MEV flip-flop will be clear for the former and set for the latter. The MEV violation register will be updated by "meu_update_violation" only if the call is NOT for an MEM violation; if it is, then the register has already been set and should not be disturbed. */ void mp_violation (void) { if (mp_control) { /* if memory protect is on */ meu_update_violation (); /* then update the MEVR (if not a MEV) */ mp_flag_buffer = SET; /* set the MP flag buffer flip-flop (if IEN) */ io_assert (&mp_dev, ioa_ENF); /* and the flag flip-flop (if IEN) */ cpu_microcode_abort (Memory_Protect); /* abort the instruction */ } return; } /* Turn memory protect off. This routine is called to disable memory protect. In hardware, MP cannot be turned off, except by causing a violation. Microcode typically does this by executing an IOG micro-order with a select code not equal to 1, followed by an IAK to clear the interrupt, and a FTCH to clear the INTPT flip-flop. Under simulation, clearing the MP control flip-flop produces the same effect. This routine also cancels any scheduled MP event service, in case it's called during execution of a microcoded trap cell instruction. */ void mp_disable (void) { mp_control = CLEAR; /* clear the control flip-flop to turn MP off */ mp_reenable = CLEAR; /* clear the MP reenable */ mp_enabled = CLEAR; /* and MP currently enabled flip-flops */ sim_cancel (mp_unit); /* cancel any pending MP reenable */ return; } /* Report the memory protect state. This routine returns TRUE if MP is on and FALSE otherwise. It is used by the RTE OS microcode executors to check the protection state. In hardware, this is done by reading the MEM status register and checking the protected mode bit (bit 11). In simulation, the MP control flip-flop is checked, as the MEM status register is not global. */ t_bool mp_is_on (void) { return (mp_control == SET); /* return TRUE if MP is on and FALSE if it is off */ } /* Report the INT (W6) jumper position. This routine returns TRUE if jumper W6 is not installed and MP is on, and FALSE otherwise. It is called when an interrupt is pending but deferred because the Interrupt Enable flip-flop is clear. If jumper W6 is installed, instructions that reference memory will hold off pending but deferred interrupts until three levels of indirection have been followed. If W6 is removed, then deferred interrupts are recognized immediately if MP is on. */ t_bool mp_reenable_interrupts (void) { return mp_unit [0].flags & UNIT_MP_INT && mp_control; /* return TRUE if interrupts are always recognized */ } /* Trace a memory protect violation. This routine is called when CPU operand tracing is enabled and the microcoded memory protect trap cell instruction is executed. It reports the reason for the interrupt (MP, MEM, or PE violation). The routine returns TRUE for a MP/MEM violation and FALSE for a PE violation. This information is used by the instruction microcode. */ t_bool mp_trace_violation (void) { tprintf (cpu_dev, TRACE_OPND, OPND_FORMAT " entry is for a %s\n", PR, IR, (mp_VR & MPVR_PARITY_ERROR ? "parity error" : (mp_mevff == SET ? "dynamic mapping violation" : "memory protect violation"))); return (mp_VR & MPVR_PARITY_ERROR) == 0; /* return TRUE for MP, FALSE for PE */ } /* Memory Protect local SCP support routines */ /* Service the memory protect accessory. This routine is scheduled whenever IAK is asserted to the MP interface, and the MP card itself is not interrupting. The purpose is to reenable memory protection if the interrupt trap cell contains a non-HLT I/O instruction. In hardware, the MP card responds to a "foreign" IAK (i.e., one acknowledging another interface's interrupt request) by disabling memory protection while the trap cell instruction is executed. If that instruction is a non-HLT IOG instruction, MP is automatically reenabled before instruction resumes at the point of interruption. Otherwise, MP remains off while the interrupt handler executes. In simulation, this is handled during IAK processing by setting "mp_enabled" to the state of the MP control flip-flop and scheduling the MP event service routine to enter after the next instruction. If the trap cell instruction is an I/O instruction, "cpu_iog" will call "mp_check_io" as part of its processing. If that routine is called for a non-HLT instruction, it sets "mp_reenable" to the value saved in "mp_enabled", i.e., "mp_reenable" will be SET if MP was enabled when the interrupt occurred (it's initialized to CLEAR). When this routine is entered after the trap instruction executes, it sets "mp_control" to the value of "mp_reenable", which reenables MP if MP was on. Implementation notes: 1. The two-level setting (mp_enabled -> mp_reenable -> mp_control) is necessary to avoid having to clear the reenable flag on every instruction execution. Consider if "mp_reenable" is set directly from "mp_control" in the IAK processor. The "mp_check_io" routine would clear it if the instruction is a HLT. But it would also have to be cleared for all other non-IOG instructions, which means inserting a "mp_reenable = CLEAR" statement in all other instruction execution paths. With the two-level setting, "mp_reenable" is set from "mp_enabled" only in the "mp_check_io" routine, and then only if the instruction is not a HLT instruction. This saves the delay inherent in clearing "mp_reenable" in the 99.99% of the cases where an IAK is not being serviced. */ static t_stat mp_service (UNIT *uptr) { mp_control = mp_reenable; /* reenable MP if a non-HLT I/O instruction was executed */ mp_reenable = CLEAR; /* clear the reenable */ mp_enabled = CLEAR; /* and enabled-at-interrupt flip-flops */ if (mp_control) { /* if MP was reenabled */ mp_mevff = CLEAR; /* then clear the Memory Expansion Violation flip-flop */ mp_evrff = SET; /* and set the Enable Violation Register flip-flop */ } return SCPE_OK; } /* Set the JSB (W5) jumper mode. This validation routine is entered with the "value" parameter set to zero or UNIT_MP_JSB, depending on whether jumper W5 is being installed or removed. The unit, character, and descriptor pointers are not used. The protected lower bound address for JSB instruction protection depends on the W5 jumper setting. If W5 is in, then the lower bound is 2, allowing JSBs to the A and B registers. If W5 is out, then the lower bound is 0, just as with JMP. */ static t_stat mp_set_jsb (UNIT *uptr, int32 value, CONST char *cptr, void *desc) { if (value == UNIT_MP_JSB) /* if jumper W5 is out */ jsb_bound = 0; /* then the protected lower bound is address 0 */ else /* otherwise W5 is installed */ jsb_bound = 2; /* so the protected bound is address 2 */ return SCPE_OK; } /* Reset memory protect. This routine is called for a RESET, RESET MP, RUN, or BOOT command. It is the simulation equivalent of an initial power-on condition (corresponding to PON, POPIO, and CRS signal assertion) or a front-panel PRESET button press (corresponding to POPIO and CRS assertion). SCP delivers a power-on reset to all devices when the simulator is started. */ static t_stat mp_reset (DEVICE *dptr) { io_assert (dptr, ioa_POPIO); /* PRESET the device (does not use PON) */ mp_mem_changed = TRUE; /* set the MP/MEM registers changed flag */ return SCPE_OK; }