/*
 * vim: filetype=c:tabstop=4:ai:expandtab
 * SPDX-License-Identifier: ICU
 * SPDX-License-Identifier: Multics
 * scspell-id: d49ab489-f62e-11ec-9ac1-80ee73e9b8e7
 *
 * ---------------------------------------------------------------------------
 *
 * Copyright (c) 2007-2013 Michael Mondy
 * Copyright (c) 2012-2016 Harry Reed
 * Copyright (c) 2013-2022 Charles Anthony
 * Copyright (c) 2021-2023 The DPS8M Development Team
 *
 * All rights reserved.
 *
 * This software is made available under the terms of the ICU
 * License, version 1.8.1 or later.  For more details, see the
 * LICENSE.md file at the top-level directory of this distribution.
 *
 * ---------------------------------------------------------------------------
 *
 * This source file may contain code comments that adapt, include, and/or
 * incorporate Multics program code and/or documentation distributed under
 * the Multics License.  In the event of any discrepancy between code
 * comments herein and the original Multics materials, the original Multics
 * materials should be considered authoritative unless otherwise noted.
 * For more details and historical background, see the LICENSE.md file at
 * the top-level directory of this distribution.
 *
 * ---------------------------------------------------------------------------
 */

//-V536

/*
 * scu.c -- System Controller
 *
 * See AN70, section 8 and GB61.
 *
 * There were a few variations of SCs and SCUs:
 * SCU -- Series 60 Level 66 Controller
 * SC -- Level 68 System Controller
 * 4MW SCU -- A later version of the Level 68 SC
 *
 * SCUs control access to memory.
 * Each SCU owns a certain range of absolute memory.
 * This emulator allows the CPU to access memory directly however.
 * SCUs contain clocks.
 * SCUS also contain facilities which allow CPUS and IOMs to communicate.
 * CPUs or IOMS request access to memory via the SCU.
 * CPUs use the CIOC instr to talk to IOMs and other CPUs via a SCU.
 * IOMs use interrupts to ask a SCU to signal a CPU.
 * Other Interesting instructions:
 * read system controller reg and set system controller reg (RSCR & SSCR)
 *
 */

/*
 * Physical Details & Interconnection -- AN70, section 8.
 *
 * SCUs have 8 ports.
 * Active modules (CPUs and IOMs) have up to four of their ports
 * connected to SCU ports.
 *
 * The 4MW SCU has eight on/off switches to enable or disable
 * the ports.  However, the associated registers allow for
 * values of enabled, disabled, and program control.
 *
 * SCUs have stores (memory banks).
 *
 * SCUs have four sets of registers controlling interrupts.  Only two
 * of these sets, designated "A" and "B" are used.  Each set has:
 * Execute interrupt mask register -- 32 bits; enables/disables
 * the corresponding execute interrupt cell
 * Interrupt mask assignment register -- 9 bits total
 * two parts: assigned bit, set of assigned ports (8 bits)
 * In Multics, only one CPU will be assigned in either mask
 * and no CPU appears in both.   Earlier hardware versions had
 * four 10-position rotary switches.  Later hardware versions had
 * two 9-position (0..7 and off) rotary switches.
 *
 * Config panel -- Level 68 6000 SCU
 * -- from AM81
 * store A and store B
 * 3 position rotary switch: on line, maint, off line
 * size: 32K, 64K, 128K, 256K
 * exec interrupt mask assignment
 * four 10-position rotary switches (A through D): off, 0 .. 7, M
 * One switch for each program interrupt register
 * Assign mask registers to system ports
 * Normally assign one mask reg to each CPU
 *
 *   AM81:
 *     "        The EXECUTE INTERRUPT MASK ASSIGNMENT (EIMA) rotary switches
 *      determine where interrupts sent to memory are directed.  The four EIMA
 *      rotary switches, one for each program interrupt register, are used to
 *      assign mask registers to system ports. The normal settings assign one
 *      mask register to each CPU configured.
 *
 *      Each switch assigns mask registers as follows:
 *
 *          Position
 *            OFF     Unassigned
 *              0     Assigned to port 0
 *                ...
 *              7     Assigned to port 7
 *              M     Assigned to maintenance panel
 *
 *      Assignment of a mask register to a system port designates the
 *      port as a control port, and that port receives interrupt present
 *      signals. Up to four system ports can be designated as control
 *      ports. The normal settings assign one mask register to each CPU
 *      configured."
 *
 *
 *
 * Config panel -- Level 68 System Controller UNIT (4MW SCU)
 * -- from AM81
 * Store A, A1, B, B1 (online/offline)
 * LWR Store Size
 * PORT ENABLE
 * Eight on/off switches
 * Should be on for each port connected to a configured CPU
 * mask/port assignment
 * Two rotary switches (A & B); set to (off, 0..7)
 * See EXEC INTERRUPT on the 6000 SCU
 * When booting, one should be set to the port connected to
 * the bootload CPU.   The other should be off.
 *
 * If memory port B of CPU C goes to SCU D, then memory port B of all
 * other CPUs *and* IOMs must go to SCU D. -- AN70, 8-4.
 *
 * The base address of the SCU is the actual memory size * the port
 * assignment. -- AN70, 8-6.
 *
 *  43A239854 6000B Eng. Prod. Spec, 3.2.7 Interrupt Multiplex Word:
 *    "The IOM has the ability to set any of the 32 program interrupt
 *     cells located in the system controller containing the base address
 *     of the IOM. It should be noted that for any given IOM identity
 *     switch setting, the IOM can set only 8 of these program interrupt
 *     cells."
 *
 */

/*
 * === Initialization and Booting -- Part 1 -- Operator's view
 *
 * Booting Instructions (GB61)
 * First boot the BCE OS (Bootload command Environment).  See below.
 * A config deck is used
 * Bootload SCU is the one with a base addr of zero.
 * BCE is on a BCE/Multics System tape
 * Booted from tape into the system via bootload console

 */

/*
 * 58009906 (DPS8)
 * When CPU needs to address the SCU (for a write/read data cycle,
 * for example), the ETMCM board int the CU of the CPU issues a $INT
 * to the SCU.  This signal is sent ... to the SCAMX active port
 * control board in the SCU
 */

// How?  If one of the 32 interrupt cells is set in one of the SCs,
// our processor will have the interrupt present (XIP) line active.
// Perhaps faults are flagged in the same way via the SXC system
// controller command.

// TEMPORARY
// Each SCU owns a certain range of absolute memory.
// CPUs use the cioc instr to talk to IOMs and other CPUs via a SCU.
// IOMs use interrupts to ask a SCU to signal a CPU.
// read system controller reg and set system controller reg (rscr & sscr)
// Bootload SCU is the one with a base addr of zero.
// 58009906
// When CPU needs to address the SCU (for a write/read data cycle,
// for example), the ETMCM board int the CU of the CPU issues a $INT
// to the SCU.  This signal is sent ... to the SCAMX active port
// control board in the
// -----------------------
// How?  If one of the 32 interrupt cells is set in one of the SCs,
// our processor will have the interrupt present (XIP) line active.
// Perhaps faults are flagged in the same way via the SXC system
// controller command.

/*
 * *** More (new) notes ***
 *
 * instr rmcm -- read mem controller mask register
 * ... for the selected controller, if the processor has a mask register
 * assigned ..
 * instr smcm -- set  mem controller mask register
 * ... for the selected controller, if the processor has a mask register
 * assigned, set it to C(AQ)
 * instr smic
 * turn on interrupt cells (any of 0..31)
 * instr cioc -- connect i/o channel, pg 173
 * SC addressed by Y sends a connect signal to the port specified
 * by C(Y)33,35
 * instr rscr & sscr -- Read/Store System Controller Register, pg 170
 *
 * 32 interrupt cells ... XIP
 * mask info
 * 8 mask registers
 * 58009906
 * =============
 *
 * AM81
 * Every active device (CPU, IOM) must be able to access all SCUs
 * Every SCU must have the same active device on the same SCU, so
 * all SCUs must have the same PORT ENABLE settings
 * Every active device must have the same SCU on the same port,
 * so all active devices will have the same config panel settings.
 * Ports must correspond -- port A on every CPU and IOM must either
 * be connected to the same SCU or not connected to any SCU.
 * IOMs should be on lower-numbered SCU ports than CPUs.
 * Multics can have 16MW words of memory.
 * CPUs have 8 ports, a..h.
 * SCUs have 8 ports, 0..7.
 *
 *
 * Level 68 6000 SCU Configuration Panel
 *   system control and monitor (cont&mon/mon/off)
 *   system boot control (on/off)
 *   alarm (disable/normal)
 *   maintenance panel mode (test/normal)
 *   store a
 *      mode (offline/maint/online)
 *      size (32k, 64k, 128k, 256k)
 *   store b
 *      mode (offline/maint/online)
 *      size (32k, 64k, 128k, 256k)
 *   execute interrupt mask assignment
 *      (A through D; off/0/1/2/3/4/5/6/7/m)
 *   [CAC] I interpret this as CPU [A..D] is connected to my port [0..7]
 *   address control
 *      lower store (a/b)
 *      offset (off, 16k, 32k, 64k)
 *      interlace (on/off)
 *   cycle port priority (on/off)
 *   port control (8 toggles) (enabled/prog cont/disable)
 *
 * The EXECUTE INTERRUPT MASK ASSIGNMENT (EIMA) rotary switches
 * determine where interrupts sent to memory are directed. The four EIMA
 * rotary switches, one for each program interrupt register, are used to
 * assign mask registers to system ports. The normal settings assign one
 * mask register to each CPU configured.
 *
 *  Assignment of a mask register to a system port designates the port as a
 *  control port, and that port receives interrupt present signals. Up to four
 *  system ports can be designated as control ports. The normal settings
 *  assign one mask register to each cpu configured.
 *
 *
 *
 * Configuration rules for Multics:
 *
 *   1. Each CPU in the system must be connected to each SCU in the system
 *
 *   2. Each IOM in the system must be connected to each SCU in the system
 *
 *   3. Each SCU in the system must be connected to every CPU and IOM in the
 *      system.
 *
 *   4. Corresponding ports on all CPUs and IOMs must be connected to the same
 *      SCU. For example, port A on every CPU and IOM must be connected to the
 *      same SCU or not connected to any SCU.
 *
 *   5. Corresponding ports on all SCUs must be connected to the same active
 *      device (CPU or IOM). For example, if port 0 on any SCU is connected to
 *      IOM A, then port 0 on all SCUs must be connected to IOM A.
 *
 *   6. IOMs should be connected to lower-number SCU ports the CPUs.
 *
 *   These rules are illustrated in Figure 3-5, where the port numbers for a
 *   small Multics system of 2 CPUS, 3 SCUs and 2 IOMs have been indicated
 *
 *
 *
 *
 *                    -----------------                      -----------------
 *                    |               |                      |               |
 *                    |     CPU A     |                      |     CPU B     |
 *                    |               |                      |               |
 *                    -----------------                      -----------------
 *                    | A | B | C | D |                      | A | B | C | D |
 *                    -----------------                      -----------------
 *                      |   |   |                              |   |   |
 *                      |   |   |                              |   |   -----------------
 *                      |   |   |                              |   |                   |
 *                      |   |   -------------------------------)---)----------------   |
 *                      |   |                                  |   |               |   |
 *   --------------------   -----------------                  |   |               |   |
 *   |                                      |                  |   |               |   |
 *   |   -----------------------------------)-------------------   |               |   |
 *   |   |                                  |                      |               |   |
 *   |   |                                  |   --------------------               |   |
 *   |   |                                  |   |                                  |   |
 * -----------------                      -----------------                      -----------------
 * | 7 | 6 | 5 | 4 |                      | 7 | 6 | 5 | 4 |                      | 7 | 6 | 5 | 4 |
 * -----------------                      -----------------                      -----------------
 * |               |                      |               |                      |               |
 * |     SCU C     |                      |     SCU B     |                      |     SCU A     |
 * |               |                      |               |                      |               |
 * -----------------                      -----------------                      -----------------
 * | 3 | 2 | 1 | 0 |                      | 3 | 2 | 1 | 0 |                      | 3 | 2 | 1 | 0 |
 * -----------------                      -----------------                      -----------------
 *           |   |                                  |   |                                  |   |
 *           |   |                                  |   -----------                        |   |
 *           |   |                                  |             |                        |   |
 *           |   -----------------------------------)---------    |                        |   |
 *           |                                      |        |    |                        |   |
 *           ----------    --------------------------        |    |                        |   |
 *                    |    |                                 |    |                        |   |
 *                    |    |   ------------------------------)----)-------------------------   |
 *                    |    |   |                             |    |                            |
 *                    |    |   |                             |    |  ---------------------------
 *                    |    |   |                             |    |  |
 *                   -----------------                      -----------------
 *                   | A | B | C | D |                      | A | B | C | D |
 *                   -----------------                      -----------------
 *                   |               |                      |               |
 *                   |     IOM A     |                      |     IOM B     |
 *                   |               |                      |               |
 *                   -----------------                      -----------------
 *
 *
 *
 *"During bootload, Multics requires a contiguous section of memory beginning at
 * absolute address 0 and sufficiently large to contain all routines and data
 * structures used during the first phase of Multics initialization (i.e.
 * collection 1).
 * The size of the section required varies among Multics release, and it also
 * depends on the size of the SST segment, which is dependent on the parameters
 * specified by the site on the SST config card. ... However
 * 512 KW is adequate for all circumstances. There can be no "holes" in memory
 * within this region. Beyond this region, "holes" can exist in memory."
 *
 *
 */

/*
 * From AN70-1 May84, pg 86 (8-6)
 *
 * RSCR SC_CFG bits 9-11 lower store size
 *
 * A DPS-8 SCU may have up to four store units attached to it. If this
 * is the case, two store units form a pair of units. The size of a
 * pair of units (or a single unit) is 32K * 2 ** (lower store size)
 * above.
 */

/*
 * From AN70-1 May84, pg 86 (8-6)
 *
 * SCU ADDRESSING
 *
 *       There are three ways in which an SCU is addressed.  In the
 * normal mode of operation (memory reading and writing), an active
 * unit (IOM or CPU) translates an absolute address into a memory
 * port (on it) and a relative memory address within the memory
 * described by the memory port. The active module sends the
 * address to the SCU on the proper memory port. If the active
 * module is enabled by the port enable mask in the referenced SCU,
 * the SCU will take the address given to it and provide the
 * necessary memory access.
 *
 *      The other two ways pertain to reading/setting control
 * registers in the SCU itself. For each of these, it is still
 * necessary to specify somehow the memory port on the CPU whose SCU
 * registers are desired. For the RMCM, SMCM and SMIC instructions,
 * this consists of providing a virtual address to the processor for
 * which bits 1 and 2 are the memory port desired.
 *
 *      The rscr and sscr instructions, though key off the final
 * absolute address to determine the SCI (or SCU store unit)
 * desired. Thus, software needs a way to translate a memory port
 * number into an absolute address to reach the SCU. This is done
 * with the paged segment scas, generated by int_scas (and
 * init_scu). scas has a page corresponding to each SCU and to each
 * store unit in each SCU. pmut$rscr and pmut$sscr use the memory
 * port number desired to generate a virtual address into scas whose
 * absolute address (courtesy of the ptws for sca) just happen to
 * describe memory within that SCU.
 *
 *       The cioc instruction (discussed below) also depends on the
 * final absolute addres of the target operand to identify the SCU
 * to perform the operation. In the case of the cioc instruction,
 * though, the has no particular impact in Multics software. All
 * target operands for the cioc instruction when referencing IOMs
 * are in the low order SCU. When referencing CPUS, the SCU
 * performing the connecting has no real bearing.
 *
 * Inter-module communication
 *
 *       As mentioned earlier, communication between active modules
 * (CPUs and IOMs can only be performed through SCUs.
 *
 *       CPUs communicate to IOMs and other CPUs via the cioc
 * (connect i/o channel) instruction. The operand of the instruction
 * is a word in memory. The SCU containing this operand is the SCU
 * that performs the connect function. The word fetched from memory
 * contains in its low order bits the identity of a port on the SCU
 * to which this connection is to be sent. This only succeeds if the
 * target port is enabled (port enable mask) on the SCU. When the
 * target of the connection is an IOM; this generates a connect strobe
 * to the IOM. The IOM examines its mailbox in memory to determine
 * its course of action. When the target of the connect is another
 * CPU, this generates a connect fault in the target processor. The
 * target processor determines what course to follow on the basis
 * of information in memory analyzed by software. When a connect is
 * sent to a process (including the processor issuing the connect),
 * the connect is deferred until the processor stops
 * executing inhibited code (instructions with the inhibit bit set).
 *
 *       Signals sent from an IOM to a CPU are much more involved.
 * The basic flow is as follows. The IOM determines an interrupt
 * number. (The interrupt number is a five bit value, from 0 to 31.
 * The high order bits are the interrupt level.
 *
 * 0 - system fault
 * 1 - terminate
 * 2 - marker
 * 3 - special
 *
 * The low order three bits determines the IOM and IOM channel
 * group.
 *
 * 0 - IOM 0 channels 32-63
 * 1 - IOM 1 channels 32-63
 * 2 - IOM 2 channels 32-63
 * 3 - IOM 3 channels 32-63
 * 4 - IOM 0 channels 0-31
 * 5 - IOM 1 channels 0-31
 * 6 - IOM 2 channels 0-31
 * 7 - IOM 3 channels 0-31
 *
 * It also takes the channel number in the group (0-31 meaning
 * either channels 0-31 to 32-63) and sets the <channel number>th
 * bit in the <interrupt number>th memory location in the interrupt
 * mask word (IMW) array in memory. It then generates a word with
 * the <interrupt number>th bit set and sends this to the bootload
 * SCU with the SC (set execute cells) SCU command. This sets the
 * execute interrupt cell register in the SCU and sends an XIP
 * (execute interrupt present) signal to various processors
 * connected to the SCU. (The details of this are covered in the
 * next section.) One of the processors (the first to get to it)
 * sends an XEC (execute interrupt cells) SCU command to the SCU who
 * generated the XIP signal. The SCU provides the interrupt number
 * to the processor, who uses it to determine the address of a fault
 * pair in memory for the "fault" caused by this interrupt. The
 * processing of the XEC command acts upon the highest priority
 * (lowest number) bit in the execute interrupt cell register, and
 * also resets this bit in the register.
 *
 * Interrupts Masks and Assignment
 *
 *       The mechanism for determining which processors are candidates
 * for receiving an interrupt from an IOM is an involved
 * topic. First of all, a processor will not be interrupted as long
 * as it is executing inhibited instructions (instructions with the
 * inhibit bit set). Beyond this, though, lies the question of
 * interrupt masks and mask assignment.
 *
 *       Internal to the SCU are two sets of registers (A and B),
 * each set consisting of the execute interrupt mask register and
 * the interrupt mask assignment register. Each execute interrupt
 * mask register is 32 bits long, with each bit enabling the
 * corresponding bit in the execute interrupt cell register. Each
 * interrupt mask assignment register has two parts, an assigned bit
 * and a set of ports to which it is assigned (8 bits). When a bit
 * is set in the execute  interrupt sells register, the SCU ANDs this
 * bit with the corresponding bit in each of the execute interrupt
 * mask registers. If the corresponding bit of execute interrupt
 * mask register A, for example, is on, the SCU then looks at the A
 * interrupt mask assignment register. If this register is not
 * assigned (enable), no further action takes place in regards to
 * the A registers. (The B registers are still considered) (in
 * parallel, by the way).) If the register is assigned (enabled)
 * then interrupts will be send to all ports (processors) whose
 * corresponding bit is set in the interrupt mask assignment
 * register. This, only certain interrupts are allowed to be
 * signalled at any given time (base on the contents of the execute
 * interrupt mask registers) and only certain processors will
 * receive these interrupts (as controlled by the interrupt mask
 * assignment registers).
 *
 *       In Multics, only one processor is listed in each of the two
 * interrupt mask assignment registers, and no processor appears in
 * both. Thus there is a one for one correspondence between
 * interrupt masks that are assigned (interrupt mask registers whose
 * assigned (enabled) bit is on) and processors who have an
 * interrupt mask (SCU port number appears in an interrupt mask
 * register). So, at any one time only two processors
 * are eligible to receive interrupts. Other processors need not
 * worry about masking interrupts.
 *
 *       The contents of the interrupt mask registers may be
 * obtained with the SCU configuration information with the rscr
 * instruction and set with the sscr instruction.
 *
 *  bits   meaning
 *
 * 00-07   ports assigned to mask A (interrupt mask assignment A)
 * 08-08   mask A is unassigned (disabled)
 * 36-43   ports assigned to mask B (interrupt mask assignment B)
 * 44-44   mask B is unassigned (disabled)
 *
 *       The contents of a execute interrupt mask register are
 * obtained with the rmcm or the rscr instruction and set with the
 * smcm or the sscr instruction. The rmcm and smcm instruction only
 * work if the processor making the request has a mask register
 * assigned to it. If not, rmcm returns zero (no interrupt are
 * enabled to it) and a smcm is ignored (actually the port mask
 * setting is still done). The rscr and sscr instructions allow the
 * examining/setting of the execute interrupt mask register for any
 * port on a SCU; these have the same effect as smcm and rmcm if the
 * SCU port being referenced does not have a mask assigned to it.
 * The format of the data returned by these instructions is as
 * follows.
 *
 *  bits   meaning
 * 00-15   execute interrupt mask register 00-15
 * 32-35   SCU port mask 0-3
 * 36-51   execute interrupt mask register 16-31
 * 68-71   SCU port mask 4-7
 *
 */

// SCU numbering:
//
// AM81-04, pg 49: "... the ports are listed in order of increasing base
//    address, which corresponds to the order of mem config cards."
// pg 97: "mem port size state ... port as a value (a through h) that
//        corresponds to the number of the active module port to which the
//        system controller is connected.
//
// From this, I conclude;
//   The SCU connected to port A (0) is SCUA, 1 B, 2 C, etc.
//   SCUA starts at address 0, and the SCUs are sorted by increasing addresses.
//

// ============================================================================

#include <sys/time.h>
#include "dps8.h"
#include "dps8_sys.h"
#include "dps8_faults.h"
#include "dps8_scu.h"
#include "dps8_iom.h"
#include "dps8_cable.h"
#include "dps8_cpu.h"
#include "dps8_utils.h"
#if defined(THREADZ) || defined(LOCKLESS)
# include "threadz.h"
#endif

#define DBG_CTR 1

scu_t scu [N_SCU_UNITS_MAX];

#define N_SCU_UNITS 1 // Default

static UNIT scu_unit [N_SCU_UNITS_MAX] = {
#ifdef NO_C_ELLIPSIS
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
  { UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
#else
  [0 ... N_SCU_UNITS_MAX-1] = {
    UDATA (NULL, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
  }
#endif
};

#define UNIT_NUM(uptr) ((uptr) - scu_unit)

// Hardware configuration switches

// sscr and other instructions override these settings

static struct config_switches
  {
    uint mode;                            // program or manual
    uint port_enable [N_SCU_PORTS];       // enable/disable
    uint mask_enable [N_ASSIGNMENTS];     // enable/disable
    uint mask_assignment [N_ASSIGNMENTS]; // assigned port number
    uint lower_store_size;                // In K words, power of 2; 32 - 4096
    uint cyclic;                          // 7 bits
    uint nea;                             // 8 bits
    uint onl;                             // 4 bits
    uint interlace;                       // 1 bit
    uint lwr;                             // 1 bit
  } config_switches [N_SCU_UNITS_MAX];

enum { MODE_MANUAL = 0, MODE_PROGRAM = 1 };

unsigned int gtod_warned = 0;

// ============================================================================

static t_stat scu_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr,
                               UNUSED int val, const UNUSED void * desc)
  {
    sim_printf("Number of SCU units in system is %d\n", scu_dev.numunits);
    return SCPE_OK;
  }

static t_stat scu_set_nunits (UNUSED UNIT * uptr, UNUSED int32 value,
                              const char * cptr, UNUSED void * desc)
  {
    if (! cptr)
      return SCPE_ARG;
    int n = atoi (cptr);
    if (n < 1 || n > N_SCU_UNITS_MAX)
      return SCPE_ARG;
    scu_dev.numunits = (uint) n;
    return SCPE_OK;
  }

static t_stat scu_show_state (UNUSED FILE * st, UNIT *uptr, UNUSED int val,
                              UNUSED const void * desc)
  {
    long scu_unit_idx = UNIT_NUM (uptr);
    if (scu_unit_idx < 0 || scu_unit_idx >= (int) scu_dev.numunits)
      {
        sim_debug (DBG_ERR, & scu_dev,
                   "scu_show_state: Invalid unit number %ld\n",
                   (long) scu_unit_idx);
        sim_printf ("error: Invalid unit number %ld\n", (long) scu_unit_idx);
        return SCPE_ARG;
      }

    sim_printf ("SCU unit number %ld\n", (long) scu_unit_idx);
    scu_t * scup = scu + scu_unit_idx;
    sim_printf ("    Mode %s\n",
                config_switches[scu_unit_idx].mode ? "PROGRAM" : "MANUAL");

    for (int i = 0; i < N_SCU_PORTS; i ++)
      {
        struct ports * pp = scup -> ports + i;

        sim_printf ("    Port %d %s dev_idx %d dev_port %d type %s\n",
                    i, scup->port_enable[i] ? "ENABLE " : "DISABLE",
                    pp->dev_idx, pp->dev_port[XXX_TEMP_SCU_SUBPORT],
                    pp->type == ADEV_NONE ? "NONE" :
                    pp->type == ADEV_CPU ? "CPU" :
                    pp->type == ADEV_IOM ? "IOM" :
                    "<enum broken>");
      }
    for (int i = 0; i < N_ASSIGNMENTS; i ++)
      {
        //struct interrupts * ip = scup -> interrupts + i;

        sim_printf ("    Cell %c\n", 'A' + i);
        sim_printf ("        exec_intr_mask %012o\n",
                    scup -> exec_intr_mask [i]);
        sim_printf ("        mask_enable %s\n",
                    scup -> mask_enable [i] ? "ENABLE" : "DISABLE");
        sim_printf ("        mask_assignment %d\n",
                    scup -> mask_assignment [i]);
        sim_printf ("        cells ");
        for (int j = 0; j < N_CELL_INTERRUPTS; j ++)
          sim_printf("%d", scup -> cells [j]);
        sim_printf ("\n");
      }
    sim_printf("Lower store size: %d\n", scup -> lower_store_size);
    sim_printf("Cyclic: %03o\n",         scup -> cyclic);
    sim_printf("NEA: %03o\n",            scup -> nea);
    sim_printf("Online: %02o\n",         scup -> onl);
    sim_printf("Interlace: %o\n",        scup -> interlace);
    sim_printf("Lower: %o\n",            scup -> lwr);
    sim_printf("ID: %o\n",               scup -> id);
    sim_printf("mode_reg: %06o\n",       scup -> mode_reg);
    sim_printf("Elapsed days: %d\n",     scup -> elapsed_days);
    sim_printf("Steady clock: %d\n",     scup -> steady_clock);
    sim_printf("Bullet time: %d\n",      scup -> bullet_time);
    sim_printf("Y2K enabled: %d\n",      scup -> y2k);
    return SCPE_OK;
  }

static t_stat scu_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
                               UNUSED int val, UNUSED const void * desc)
{
    static const char * map [N_SCU_PORTS] =
      {
        "0", "1", "2", "3", "4", "5", "6", "7"
      };
    long scu_unit_idx = UNIT_NUM (uptr);
    if (scu_unit_idx < 0 || scu_unit_idx >= (int) scu_dev.numunits)
      {
        sim_debug (DBG_ERR, & scu_dev,
                   "scu_show_config: Invalid unit number %ld\n",
                   (long) scu_unit_idx);
        sim_printf ("error: Invalid unit number %ld\n", (long) scu_unit_idx);
        return SCPE_ARG;
      }

    sim_printf ("SCU unit number %ld\n", (long) scu_unit_idx);

    struct config_switches * sw = config_switches + scu_unit_idx;

    const char * mode = "<out of range>";
    switch (sw -> mode)
      {
        case MODE_PROGRAM:
          mode = "Program";
          break;
        case MODE_MANUAL:
          mode = "Manual";
          break;
      }

    sim_printf ("Mode:                       %s\n", mode);
    sim_printf ("Port Enable:             ");
    for (int i = 0; i < N_SCU_PORTS; i ++)
      sim_printf (" %3o", sw -> port_enable [i]);
    sim_printf ("\n");
    for (int i = 0; i < N_ASSIGNMENTS; i ++)
      {
        sim_printf ("Mask %c:                     %s\n",
                    'A' + i,
                    sw->mask_enable[i] ? (map[sw->mask_assignment[i]]) : "Off");
      }
    sim_printf ("Lower Store Size:           %o\n",   sw -> lower_store_size);
    sim_printf ("Cyclic:                     %03o\n", sw -> cyclic);
    sim_printf ("Non-existent address:       %03o\n", sw -> nea);

    return SCPE_OK;
  }

//
// set scu0 config=<blah> [;<blah>]
//
//    blah =
//           mode=  manual | program
//           mask[A|B] = off | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
//           portN = enable | disable
//           lwrstoresize = 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | 4096
//           cyclic = n
//           nea = n
//
//      o  nea is not implemented; will read as "nea off"
//      o  Multics sets cyclic priority explicitly; config
//         switches are ignored.
//      o  STORE A, A1, B, B1 ONLINE/OFFLINE not implemented;
//         will always read online.
//      o  store size if not enforced; a full memory complement
//         is provided.
//      o  interlace not implemented; will read as 'off'
//      o  LOWER STORE A/B not implemented.
//      o  MASK is 'MASK/PORT ASSIGNMENT' analogous to the
//         'EXECUTE INTERRUPT MASK ASSIGNMENT of a 6000 SCU

static config_value_list_t cfg_mode_list [] =
  {
    { "manual",  0 },
    { "program", 1 },
    { NULL,      0 }
  };

static config_value_list_t cfg_mask_list [] =
  {
    { "off", -1 },
    { NULL,  0  }
  };

static config_value_list_t cfg_able_list [] =
  {
    { "disable", 0 },
    { "enable",  1 },
    { NULL,      0 }
  };

static config_value_list_t cfg_size_list [] =
  {
    { "32",    0 },
    { "64",    1 },
    { "128",   2 },
    { "256",   3 },
    { "512",   4 },
    { "1024",  5 },
    { "2048",  6 },
    { "4096",  7 },
    { "32K",   0 },
    { "64K",   1 },
    { "128K",  2 },
    { "256K",  3 },
    { "512K",  4 },
    { "1024K", 5 },
    { "2048K", 6 },
    { "4096K", 7 },
    { "1M",    5 },
    { "2M",    6 },
    { "4M",    7 },
    { NULL,    0 }
  };

static config_value_list_t cfg_on_off [] =
  {
    { "off",     0 },
    { "on",      1 },
    { "disable", 0 },
    { "enable",  1 },
    { NULL,      0 }
  };

static config_list_t scu_config_list [] =
  {
    /*  0 */ { "mode",         1, 0,               cfg_mode_list },
    /*  1 */ { "maska",        0, N_SCU_PORTS - 1, cfg_mask_list },
    /*  2 */ { "maskb",        0, N_SCU_PORTS - 1, cfg_mask_list },
    /*  3 */ { "port0",        1, 0,               cfg_able_list },
    /*  4 */ { "port1",        1, 0,               cfg_able_list },
    /*  5 */ { "port2",        1, 0,               cfg_able_list },
    /*  6 */ { "port3",        1, 0,               cfg_able_list },
    /*  7 */ { "port4",        1, 0,               cfg_able_list },
    /*  8 */ { "port5",        1, 0,               cfg_able_list },
    /*  9 */ { "port6",        1, 0,               cfg_able_list },
    /* 10 */ { "port7",        1, 0,               cfg_able_list },
    /* 11 */ { "lwrstoresize", 0, 7,               cfg_size_list },
    /* 12 */ { "cyclic",       0, 0177,            NULL          },
    /* 13 */ { "nea",          0, 0377,            NULL          },
    // mask: 8 a_online, 4 a1_online, 2 b_online, 1, b1_online
    /* 14 */ { "onl",          0, 017,             NULL          },
    /* 15 */ { "int",          0, 1,               NULL          },
    /* 16 */ { "lwr",          0, 1,               NULL          },

    // Hacks

    /* 17 */ { "elapsed_days", 0, 20000,           NULL       },
    /* 18 */ { "steady_clock", 0, 1,               cfg_on_off },
    /* 19 */ { "bullet_time",  0, 1,               cfg_on_off },
    /* 20 */ { "y2k",          0, 1,               cfg_on_off },
             { NULL,           0, 0,               NULL       }
  };

static t_stat scu_set_config (UNIT * uptr, UNUSED int32 value,
                              const char * cptr, UNUSED void * desc)
  {
    long scu_unit_idx = UNIT_NUM (uptr);
    if (scu_unit_idx < 0 || scu_unit_idx >= (int) scu_dev.numunits)
      {
        sim_debug (DBG_ERR, & scu_dev,
                   "scu_set_config: Invalid unit number %ld\n", (long) scu_unit_idx);
        sim_printf ("error: scu_set_config: Invalid unit number %ld\n",
                    (long) scu_unit_idx);
        return SCPE_ARG;
      }

    struct config_switches * sw = config_switches + scu_unit_idx;

    config_state_t cfg_state = { NULL, NULL };

    for (;;)
      {
        int64_t v;
        int rc = cfg_parse ("scu_set_config", cptr, scu_config_list,
                           & cfg_state, & v);
        if (rc == -1) // done
          break;

        if (rc == -2) // error
          {
            cfg_parse_done (& cfg_state);
            return SCPE_ARG;
          }

        const char * p = scu_config_list [rc].name;
        if (strcmp (p, "mode") == 0)
          sw -> mode = (uint) v;
        else if (strcmp (p, "maska") == 0)
          {
            if (v == -1)
              sw -> mask_enable [0] = false;
            else
              {
                sw -> mask_enable [0] = true;
                sw -> mask_assignment [0] = (uint) v;
              }
          }
        else if (strcmp (p, "maskb") == 0)
          {
            if (v == -1)
              sw -> mask_enable [1] = false;
            else
              {
                sw -> mask_enable [1] = true;
                sw -> mask_assignment [1] = (uint) v;
              }
          }
        else if (strcmp (p, "port0") == 0)
          sw -> port_enable [0] = (uint) v;
        else if (strcmp (p, "port1") == 0)
          sw -> port_enable [1] = (uint) v;
        else if (strcmp (p, "port2") == 0)
          sw -> port_enable [2] = (uint) v;
        else if (strcmp (p, "port3") == 0)
          sw -> port_enable [3] = (uint) v;
        else if (strcmp (p, "port4") == 0)
          sw -> port_enable [4] = (uint) v;
        else if (strcmp (p, "port5") == 0)
          sw -> port_enable [5] = (uint) v;
        else if (strcmp (p, "port6") == 0)
          sw -> port_enable [6] = (uint) v;
        else if (strcmp (p, "port7") == 0)
          sw -> port_enable [7] = (uint) v;
        else if (strcmp (p, "lwrstoresize") == 0)
          sw -> lower_store_size = (uint) v;
        else if (strcmp (p, "cyclic") == 0)
          sw -> cyclic = (uint) v;
        else if (strcmp (p, "nea") == 0)
          sw -> nea = (uint) v;
        else if (strcmp (p, "onl") == 0)
          sw -> onl = (uint) v;
        else if (strcmp (p, "int") == 0)
          sw -> interlace = (uint) v;
        else if (strcmp (p, "lwr") == 0)
          sw -> lwr = (uint) v;
        else if (strcmp (p, "elapsed_days") == 0)
          scu [scu_unit_idx].elapsed_days = (uint) v;
        else if (strcmp (p, "steady_clock") == 0)
          scu [scu_unit_idx].steady_clock = (uint) v;
        else if (strcmp (p, "bullet_time") == 0)
          scu [scu_unit_idx].bullet_time = (uint) v;
        else if (strcmp (p, "y2k") == 0)
          scu [scu_unit_idx].y2k = (uint) v;
        else
          {
            sim_printf ("error: scu_set_config: invalid cfg_parse rc <%d>\n",
                         rc);
            cfg_parse_done (& cfg_state);
            return SCPE_ARG;
          }
      } // process statements
    cfg_parse_done (& cfg_state);
    return SCPE_OK;
  }

static MTAB scu_mod [] =
  {
    {
      MTAB_XTD | MTAB_VUN | \
      MTAB_NMO | MTAB_VALR,                          /* Mask               */
      0,                                             /* Match              */
      (char *) "CONFIG",                             /* Print string       */
      (char *) "CONFIG",                             /* Match string       */
      scu_set_config,                                /* Validation routine */
      scu_show_config,                               /* Display routine    */
      NULL,                                          /* Value descriptor   */
      NULL                                           /* Help               */
    },
    {
      MTAB_XTD | MTAB_VDV | \
      MTAB_NMO | MTAB_VALR,                          /* Mask               */
      0,                                             /* Match              */
      (char *) "NUNITS",                             /* Print string       */
      (char *) "NUNITS",                             /* Match string       */
      scu_set_nunits,                                /* Validation routine */
      scu_show_nunits,                               /* Display routine    */
      (char *) "Number of SCU units in the system",  /* Value descriptor   */
      NULL                                           /* Help               */
    },
    {
      MTAB_XTD | MTAB_VUN | \
      MTAB_NMO | MTAB_VALR,                          /* Mask               */
      0,                                             /* Match              */
      (char *) "STATE",                              /* Print string       */
      (char *) "STATE",                              /* Match string       */
      NULL,                                          /* Validation routine */
      scu_show_state,                                /* Display routine    */
      (char *) "SCU unit internal state",            /* Value descriptor   */
      NULL                                           /* Help               */
    },
    {
      MTAB_XTD | MTAB_VUN | \
      MTAB_NMO | MTAB_VALR,                          /* Mask               */
      0,                                             /* Match              */
      (char *) "RESET",                              /* Print string       */
      (char *) "RESET",                              /* Match string       */
      scu_reset_unit,                                /* Validation routine */
      NULL,                                          /* Display routine    */
      (char *) "reset SCU unit",                     /* Value descriptor   */
      NULL                                           /* Help               */
    },
    {
      0, 0, NULL, NULL, NULL, NULL, NULL, NULL
    }
  };

//static t_stat scu_reset (DEVICE *dptr);

static DEBTAB scu_dt [] =
  {
    { (char *) "TRACE",  DBG_TRACE,  NULL },
    { (char *) "NOTIFY", DBG_NOTIFY, NULL },
    { (char *) "INFO",   DBG_INFO,   NULL },
    { (char *) "ERR",    DBG_ERR,    NULL },
    { (char *) "WARN",   DBG_WARN,   NULL },
    { (char *) "DEBUG",  DBG_DEBUG,  NULL },
    { (char *) "INTR",   DBG_INTR,   NULL }, // Don't move as it messes up DBG messages
    { (char *) "ALL",    DBG_ALL,    NULL },
    {  NULL,             0,          NULL }
  };

DEVICE scu_dev =
  {
    (char *) "SCU",  /* Name                */
    scu_unit,        /* Units               */
    NULL,            /* Registers           */
    scu_mod,         /* Modifiers           */
    N_SCU_UNITS,     /* #Units              */
    10,              /* Address radix       */
    8,               /* Address width       */
    1,               /* Address increment   */
    8,               /* Data radix          */
    8,               /* Data width          */
    NULL,            /* Examine routine     */
    NULL,            /* Deposit routine     */
    & scu_reset,     /* Reset routine       */
    NULL,            /* Boot routine        */
    NULL,            /* Attach routine      */
    NULL,            /* Detach routine      */
    NULL,            /* Context             */
    DEV_DEBUG,       /* Flags               */
    0,               /* Debug control flags */
    scu_dt,          /* Debug flag names    */
    NULL,            /* Memory size change  */
    NULL,            /* Logical name        */
    NULL,            /* Help                */
    NULL,            /* Attach_help         */
    NULL,            /* Help_ctx            */
    NULL,            /* Description         */
    NULL             /* End                 */
  };

static void dump_intr_regs (char * ctx, uint scu_unit_idx)
  {
    scu_t * up = scu + scu_unit_idx;

    sim_debug (DBG_DEBUG, & scu_dev,
               "%s A: mask %011o enable %o assignment %o\n",
               ctx, up -> exec_intr_mask [0], up -> mask_enable [0],
               up -> mask_assignment [0]);
    sim_debug (DBG_DEBUG, & scu_dev,
               "%s B: mask %011o enable %o assignment %o\n",
               ctx, up -> exec_intr_mask [1], up -> mask_enable [1],
               up -> mask_assignment [1]);
#if 0
    sim_debug (DBG_DEBUG, & scu_dev, "%s enabled ports:", ctx);
    for (uint p = 0; p < N_SCU_PORTS; p ++)
      if (up -> port_enable [p])
        {
          sim_debug (DBG_DEBUG, & scu_dev, " %d", p);
        }
    sim_debug (DBG_DEBUG, & scu_dev, "\n");

    sim_debug (DBG_DEBUG, & scu_dev, "%s set cells:", ctx);
    for (uint i = 0; i < N_CELL_INTERRUPTS; i ++)
      if (up -> cells [i])
        {
          sim_debug (DBG_DEBUG, & scu_dev, " %d", i);
        }
    sim_debug (DBG_DEBUG, & scu_dev, "\n");
#endif
#if 0
 {
    scu_t * up = scu + scu_unit_idx;

    sim_printf (
               "%s A: mask %011o enable %o assignment %o\n",
               ctx, up -> exec_intr_mask [0], up -> mask_enable [0],
               up -> mask_assignment [0]);
    sim_printf (
               "%s B: mask %011o enable %o assignment %o\n",
               ctx, up -> exec_intr_mask [1], up -> mask_enable [1],
               up -> mask_assignment [1]);

    sim_printf ("%s enabled ports:", ctx);
    for (uint p = 0; p < N_SCU_PORTS; p ++)
      if (up -> port_enable [p])
        {
          sim_printf (" %d", p);
        }
    sim_printf ("\n");

    sim_printf ("%s set cells:", ctx);
    for (uint i = 0; i < N_CELL_INTERRUPTS; i ++)
      if (up -> cells [i])
        {
          sim_printf (" %d", i);
        }
    sim_printf ("\n");
  }
#endif
   }

void scu_unit_reset (int scu_unit_idx)
  {
    scu_t * up = scu + scu_unit_idx;
    struct config_switches * sw = config_switches + scu_unit_idx;

    for (int i = 0; i < N_SCU_PORTS; i ++)
      {
        up -> port_enable [i] = sw -> port_enable [i];
      }

    for (int i = 0; i < N_ASSIGNMENTS; i ++)
      {
        up -> mask_enable [i]     = sw -> mask_enable [i];
        up -> mask_assignment [i] = sw -> mask_assignment [i];
      }
    up -> lower_store_size = sw -> lower_store_size;
    up -> cyclic           = sw -> cyclic;
    up -> nea              = sw -> nea;
    up -> onl              = sw -> onl;
    up -> interlace        = sw -> interlace;
    up -> lwr              = sw -> lwr;

// This is to allow the CPU reset to update the memory map. IAC clears the
// attached SCUs; they clear the attached IOMs.

    for (uint port_num = 0; port_num < N_SCU_PORTS; port_num ++)
      {
        struct ports * portp = & scu [scu_unit_idx].ports [port_num];
        if (portp->type != ADEV_IOM)
          continue;
        //if (! scu [scu_unit_idx].port_enable [scu_port_num])
          //continue;
        iom_unit_reset_idx ((uint) portp->dev_idx);
      }

// CAC - These settings were reversed engineer from the code instead
// of from the documentation. In case of issues, try fixing these, not the
// code.

    for (int i = 0; i < N_ASSIGNMENTS; i ++)
      {
        // XXX Hack for t4d
        up -> exec_intr_mask [i] = 037777777777;
      }
  }

t_stat scu_reset (UNUSED DEVICE * dptr)
  {
    // On reset, instantiate the config switch settings

    for (int scu_unit_idx = 0; scu_unit_idx < N_SCU_UNITS_MAX; scu_unit_idx ++)
      scu_unit_reset (scu_unit_idx);
    return SCPE_OK;
  }

// ============================================================================

#if defined(THREADZ) || defined(LOCKLESS)
static pthread_mutex_t clock_lock = PTHREAD_MUTEX_INITIALIZER;
#endif

// The SCU clock is 52 bits long; fits in t_uint64
static uint64 set_SCU_clock (uint scu_unit_idx)
  {
#if defined(THREADZ) || defined(LOCKLESS)
    pthread_mutex_lock (& clock_lock);
#endif

// The emulator supports two clock models: steady and real
// In steady mode the time of day is coupled to the instruction clock,
// allowing reproducible behavior. In real, the clock is
// coupled to the actual time-of-day.

    if (scu [0].steady_clock)
      {
        // The is a bit of code that is waiting for 5000 ms; this
        // fools into going faster
#ifdef NEED_128
        uint128 big = construct_128 (0, cpu.instrCnt);
        // Sync up the clock and the TR; see wiki page "CAC 08-Oct-2014"
        //big *= 4u;
        big = lshift_128 (big, 2);
        if (scu [0].bullet_time)
          big = multiply_128 (big, construct_128 (0, 10000u));

        //big += scu [0].elapsed_days * 1000000llu * 60llu * 60llu * 24llu;
        uint128 days = construct_128 (0, scu[0].elapsed_days);
        days         = multiply_128 (days, construct_128 (0, 1000000));
        days         = multiply_128 (days, construct_128 (0, 60 * 60 * 24));
        big          = add_128 (big, days);
#else
        __uint128_t big = cpu.instrCnt;
        // Sync up the clock and the TR; see wiki page "CAC 08-Oct-2014"
        big *= 4u;
        //big /= 100u;
        if (scu [0].bullet_time)
          big *= 10000;

        big += scu [0].elapsed_days * 1000000llu * 60llu * 60llu * 24llu;
#endif

        // Boot time

        // load_fnp is complaining that FNP core image is more than 5 years old; try
        // moving the 'boot time' back to MR12.3 release date. (12/89 according to
        // https://www.multicians.org/chrono.html

        // date -d "1990-01-01 00:00:00 -9" +%s
        // 631184400
        // For debugging MR12.3 and earlier with steady_clock, uncomment --
        // uint64 UNIX_secs = 631184400;

        // Otherwise, we'll use the current time as the steady_clock starting point --
        uint64 UNIX_secs = (uint64)time(NULL);

#ifdef NEED_128
        uint64 UNIX_usecs = UNIX_secs * 1000000llu + big.l;
#else
        uint64 UNIX_usecs = UNIX_secs * 1000000llu + (uint64) big;
#endif
        // now determine uSecs since Jan 1, 1901 ...
        uint64 Multics_usecs = 2177452800000000llu + UNIX_usecs;

        // The casting to uint show be okay; both are 64 bit, so if
        // user_correction is <0, it will come out in the wash ok.
        Multics_usecs += (uint64) scu [scu_unit_idx].user_correction;

        // The get calendar clock function is guaranteed to return
        // different values on successive calls.

        if (scu [scu_unit_idx].last_time >= Multics_usecs)
          {
            sim_debug (DBG_TRACE, & scu_dev, "finagle clock\n");
            Multics_usecs = scu [scu_unit_idx].last_time + 1;
          }
        scu [scu_unit_idx].last_time = Multics_usecs;
        goto done;
      }

    // The calendar clock consists of a 52-bit register which counts
    // microseconds and is readable as a double-precision integer by a
    // single instruction from any central processor. This rate is in
    // the same order of magnitude as the instruction processing rate of
    // the GE-645, so that timing of 10-instruction subroutines is
    // meaningful. The register is wide enough that overflow requires
    // several tens of years; thus it serves as a calendar containing
    // the number of microseconds since 0000 GMT, January 1, 1901
    ///  Secs from Jan 1, 1901 to Jan 1, 1970 - 2 177 452 800 Seconds
    /// uSecs from Jan 1, 1901 to Jan 1, 1970 - 2 177 452 800 000 000 uSeconds

    struct timeval now;
    gettimeofday(& now, NULL);

    if (scu [0].y2k) // Apply clock skew when Y2K mode enabled
      {
        // Back the clock up to just after the MR12.5 release
        // $ date --date='30 years ago' +%s ; date +%s
        // 1685451324
        // 7738766524
        now.tv_sec -= (1685451324 - 738766524); // XXX(jhj): make dynamic!
      }
    uint64 UNIX_secs  = (uint64) now.tv_sec;
    uint64 UNIX_usecs = UNIX_secs * 1000000LL + (uint64) now.tv_usec;

    static uint64 last_UNIX_usecs = 0;
    if ( (!sim_quiet) && (UNIX_usecs < last_UNIX_usecs))
      {
        if (gtod_warned < 11)
          {
            sim_warn ("\rHost clock went backwards %llu uS!\r\n",
                      (unsigned long long)(last_UNIX_usecs - UNIX_usecs));
            gtod_warned++;
          }
        else if (gtod_warned == 11)
          {
            sim_warn ("\rHost clock went backwards %llu uS!  Suppressing further warnings.\r\n",
                      (unsigned long long)(last_UNIX_usecs - UNIX_usecs));
            gtod_warned++;
          }
      }
    last_UNIX_usecs = UNIX_usecs;

    // now determine uSecs since Jan 1, 1901 ...
    uint64 Multics_usecs = 2177452800000000LL + UNIX_usecs;

    // Correction factor from the set time command

    // The casting to uint show be okay; both are 64 bit, so if
    // user_correction is <0, it will come out in the wash ok.
    Multics_usecs += (uint64) scu [scu_unit_idx].user_correction;

    if (scu [scu_unit_idx].last_time >= Multics_usecs)
        Multics_usecs = scu [scu_unit_idx].last_time + 1;
    scu [scu_unit_idx].last_time = Multics_usecs;

done:
#if defined(THREADZ) || defined(LOCKLESS)
    pthread_mutex_unlock (& clock_lock);
#endif

    return scu [scu_unit_idx].last_time;

  }

//static char pcellb [N_CELL_INTERRUPTS + 1];
static char * pcells (uint scu_unit_idx, char * buf)
  {
    for (uint i = 0; i < N_CELL_INTERRUPTS; i ++)
      {
        if (scu [scu_unit_idx].cells [i])
          buf [i] = '1';
        else
          buf [i] = '0';
      }
    buf [N_CELL_INTERRUPTS] = '\0';
    return buf;
  }

// Either an interrupt has arrived on a port, or a mask register has
// been updated. Bring the CPU up date on the interrupts.

// threadz notes:
//
// deliver_interrupts is called either from a CPU instruction or from
// IOM set_general_interrupt on the IOM thread.
//
// potential race conditions:
//   CPU variables: XIP
//   SCU variables: cells, mask_enable, exec_intr_mask, mask assignment

// Always called with SCU lock set

static void deliver_interrupts (uint scu_unit_idx)
  {
    sim_debug (DBG_DEBUG, & scu_dev, "deliver_interrupts %o\n", scu_unit_idx);
    for (uint cpun = 0; cpun < cpu_dev.numunits; cpun ++)
      {
        cpus[cpun].events.XIP[scu_unit_idx] = false;
      }

// If the CIOC generates marker and terminate interrupts, they will be posted simultaneously.
// Since the interrupts are recognized by priority and terminate has a higher priority then
// marker, if will be delivered first. The following code will deliver marker before terminate.

#ifdef REORDER
    for (uint jnum = 0; jnum < N_CELL_INTERRUPTS; jnum ++)
      {
        static const uint reorder[N_CELL_INTERRUPTS] = {
           0,  1,  2,  3,  4,  5,  6,  7,
          16, 17, 18, 29, 20, 21, 22, 23,
           8,  9, 10, 11, 12, 13, 14, 15,
          25, 25, 26, 27, 28, 29, 30, 31 };
        uint inum = reorder[jnum];
        if (! scu [scu_unit_idx].cells [inum])
          continue; //
        sim_debug (DBG_DEBUG, & scu_dev, "trying to deliver %d\n", inum);
        sim_debug (DBG_INTR, & scu_dev,
                   "scu %u trying to deliver %d\n", scu_unit_idx, inum);

        for (uint pima = 0; pima < N_ASSIGNMENTS; pima ++) // A, B
          {
            //sim_debug (DBG_DEBUG, & scu_dev,
            //           "trying inum %u pima %u enable %u\n"
            //           , inum, pima, scu [scu_unit_idx].mask_enable [pima]);
            if (scu [scu_unit_idx].mask_enable [pima] == 0)
              continue;
            uint mask = scu [scu_unit_idx].exec_intr_mask [pima];
            uint port = scu [scu_unit_idx].mask_assignment [pima];
            //sim_debug (DBG_DEBUG, & scu_dev,
            //           "mask %u port %u type %u cells %o\n",
            //           mask, port, scu [scu_unit_idx].ports [port].type,
            //           scu [scu_unit_idx].cells [inum]);
            if (scu [scu_unit_idx].ports [port].type != ADEV_CPU)
              continue;
            if ((mask & (1u << (31 - inum))) != 0)
              {
                uint sn = 0;
                if (scu[scu_unit_idx].ports[port].is_exp)
                  {
                    sn = (uint) scu[scu_unit_idx].ports[port].xipmaskval;
                    if (sn >= N_SCU_SUBPORTS)
                      {
                        sim_warn ("XIP mask not set; defaulting to subport 0\n");
                        sn = 0;
                      }
                  }
                if (! cables->scu_to_cpu[scu_unit_idx][port][sn].in_use)
                  {
                    sim_warn ("bad scu_unit_idx %u\n", scu_unit_idx);
                    continue;
                  }
                uint cpu_unit_udx = cables->scu_to_cpu[scu_unit_idx][port][sn].cpu_unit_idx;
# if defined(THREADZ) || defined(LOCKLESS)
                cpus[cpu_unit_udx].events.XIP[scu_unit_idx] = true;
#  ifdef TESTING
                HDBGIntrSet (inum, cpu_unit_udx, scu_unit_idx, __func__);
#  endif
                createCPUThread((uint) cpu_unit_udx);
#  ifndef NO_TIMEWAIT
                wakeCPU ((uint) cpu_unit_udx);
#  endif
                sim_debug (DBG_DEBUG, & scu_dev,
                           "interrupt set for CPU %d SCU %d\n",
                           cpu_unit_udx, scu_unit_idx);
# else // ! THREADZ
//if (cpu_unit_udx && ! cpu.isRunning) sim_printf ("starting CPU %c\n", cpu_unit_udx + 'A');
#  ifdef ROUND_ROBIN
                cpus[cpu_unit_udx].isRunning = true;
#  endif
                cpus[cpu_unit_udx].events.XIP[scu_unit_idx] = true;
sim_debug (DBG_DEBUG, & scu_dev, "interrupt set for CPU %d SCU %d\n", cpu_unit_udx, scu_unit_idx);
                sim_debug (DBG_INTR, & scu_dev,
                           "XIP set for SCU %d\n", scu_unit_idx);
# endif // ! THREADZ
              }
          }
      }
#else // !REORDER
    for (uint inum = 0; inum < N_CELL_INTERRUPTS; inum ++)
      {
        if (! scu [scu_unit_idx].cells [inum])
          continue; //
        sim_debug (DBG_DEBUG, & scu_dev, "trying to deliver %d\n", inum);
        sim_debug (DBG_INTR, & scu_dev,
                   "scu %u trying to deliver %d\n", scu_unit_idx, inum);

        for (uint pima = 0; pima < N_ASSIGNMENTS; pima ++) // A, B
          {
            //sim_debug (DBG_DEBUG, & scu_dev,
            //           "trying inum %u pima %u enable %u\n"
            //           , inum, pima, scu [scu_unit_idx].mask_enable [pima]);
            if (scu [scu_unit_idx].mask_enable [pima] == 0)
              continue;
            uint mask = scu [scu_unit_idx].exec_intr_mask [pima];
            uint port = scu [scu_unit_idx].mask_assignment [pima];
            //sim_debug (DBG_DEBUG, & scu_dev,
            //           "mask %u port %u type %u cells %o\n",
            //           mask, port, scu [scu_unit_idx].ports [port].type,
            //           scu [scu_unit_idx].cells [inum]);
            if (scu [scu_unit_idx].ports [port].type != ADEV_CPU)
              continue;
            if ((mask & (1u << (31 - inum))) != 0)
              {
                uint sn = 0;
                if (scu[scu_unit_idx].ports[port].is_exp)
                  {
                    sn = (uint) scu[scu_unit_idx].ports[port].xipmaskval;
                    if (sn >= N_SCU_SUBPORTS)
                      {
                        sim_warn ("XIP mask not set; defaulting to subport 0\n");
                        sn = 0;
                      }
                  }
                if (! cables->scu_to_cpu[scu_unit_idx][port][sn].in_use)
                  {
                    sim_warn ("bad scu_unit_idx %u\n", scu_unit_idx);
                    continue;
                  }
                uint cpu_unit_udx = cables->scu_to_cpu[scu_unit_idx][port][sn].cpu_unit_idx;
# if defined(THREADZ) || defined(LOCKLESS)
                cpus[cpu_unit_udx].events.XIP[scu_unit_idx] = true;
#  ifdef TESTING
                HDBGIntrSet (inum, cpu_unit_udx, scu_unit_idx, __func__);
#  endif
                createCPUThread((uint) cpu_unit_udx);
#  ifndef NO_TIMEWAIT
                wakeCPU ((uint) cpu_unit_udx);
#  endif
                sim_debug (DBG_DEBUG, & scu_dev,
                           "interrupt set for CPU %d SCU %d\n",
                           cpu_unit_udx, scu_unit_idx);
# else // ! THREADZ
//if (cpu_unit_udx && ! cpu.isRunning) sim_printf ("starting CPU %c\n", cpu_unit_udx + 'A');
#  ifdef ROUND_ROBIN
                cpus[cpu_unit_udx].isRunning = true;
#  endif
                cpus[cpu_unit_udx].events.XIP[scu_unit_idx] = true;
sim_debug (DBG_DEBUG, & scu_dev, "interrupt set for CPU %d SCU %d\n", cpu_unit_udx, scu_unit_idx);
                sim_debug (DBG_INTR, & scu_dev,
                           "XIP set for SCU %d\n", scu_unit_idx);
# endif // ! THREADZ
              }
          }
      }
#endif // REORDER
  }

t_stat scu_smic (uint scu_unit_idx, uint UNUSED cpu_unit_udx,
                 uint UNUSED cpu_port_num, word36 rega)
  {
#if defined(THREADZ) || defined(LOCKLESS)
    lock_scu ();
#endif
// smic can set cells but not reset them...
#if 1
    if (getbits36_1 (rega, 35))
      {
        for (uint i = 0; i < 16; i ++)
          {
            if (getbits36_1 (rega, i))
              scu [scu_unit_idx].cells [i + 16] = 1;
          }
        char pcellb [N_CELL_INTERRUPTS + 1];
        sim_debug (DBG_TRACE, & scu_dev,
                   "SMIC low: Unit %u Cells: %s\n",
                   scu_unit_idx, pcells (scu_unit_idx, pcellb));
      }
    else
      {
        for (uint i = 0; i < 16; i ++)
          {
            if (getbits36_1 (rega, i))
              scu [scu_unit_idx].cells [i] = 1;
          }
        char pcellb [N_CELL_INTERRUPTS + 1];
        sim_debug (DBG_TRACE, & scu_dev,
                   "SMIC high: Unit %d Cells: %s\n",
                   scu_unit_idx, pcells (scu_unit_idx, pcellb));
      }
#else
    if (getbits36_1 (rega, 35))
      {
        for (uint i = 0; i < 16; i ++)
          {
            scu [scu_unit_idx].cells [i + 16] = getbits36_1 (rega, i) ? 1 : 0;
          }
        char pcellb [N_CELL_INTERRUPTS + 1];
        sim_debug (DBG_TRACE, & scu_dev,
                   "SMIC low: Unit %u Cells: %s\n",
                   scu_unit_idx, pcells (scu_unit_idx, pcellb));
      }
    else
      {
        for (uint i = 0; i < 16; i ++)
          {
            scu [scu_unit_idx].cells [i] =
              getbits36_1 (rega, i) ? 1 : 0;
          }
        char pcellb [N_CELL_INTERRUPTS + 1];
        sim_debug (DBG_TRACE, & scu_dev,
                   "SMIC high: Unit %d Cells: %s\n",
                   scu_unit_idx, pcells (scu_unit_idx, pcellb));
      }
#endif
    dump_intr_regs ("smic", scu_unit_idx);
    deliver_interrupts (scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
    unlock_scu ();
#endif
    return SCPE_OK;
  }

// system controller and the function to be performed as follows:
//
//  Effective  Function
//  Address
//  y0000x     C(system controller mode register) -> C(AQ)
//  y0001x     C(system controller configuration switches) -> C(AQ)
//  y0002x     C(mask register assigned to port 0) -> C(AQ)
//  y0012x     C(mask register assigned to port 1) -> C(AQ)
//  y0022x     C(mask register assigned to port 2) -> C(AQ)
//  y0032x     C(mask register assigned to port 3) -> C(AQ)
//  y0042x     C(mask register assigned to port 4) -> C(AQ)
//  y0052x     C(mask register assigned to port 5) -> C(AQ)
//  y0062x     C(mask register assigned to port 6) -> C(AQ)
//  y0072x     C(mask register assigned to port 7) -> C(AQ)
//  y0003x     C(interrupt cells) -> C(AQ)
//
//  y0004x
//    or       C(calendar clock) -> C(AQ)
//  y0005x
//
//  y0006x
//    or C(store unit mode register) -> C(AQ)
//  y0007x
//
// where: y = value of C(TPR.CA)0,2 (C(TPR.CA)1,2 for the DPS 8M
// processor) used to select the system controller
// x = any octal digit
//

t_stat scu_sscr (uint scu_unit_idx, UNUSED uint cpu_unit_udx,
                 UNUSED uint cpu_port_num, word18 addr,
                 word36 rega, word36 regq)
  {
    sim_debug (DBG_DEBUG, & scu_dev, "sscr SCU unit %o\n", scu_unit_idx);

    // Only valid for a 4MW SCU

    if (scu_unit_idx >= scu_dev.numunits)
      {
// XXX should this be a store fault?
        sim_warn ("%s: scu_unit_idx out of range %d\n",
                   __func__, scu_unit_idx);
        return SCPE_OK;
      }

    // BCE uses clever addressing schemes to select SCUs; it appears we need
    // to be more selecting in picking out the function bits;
    //uint function = (addr >> 3) & 07777;
    uint function = (addr >> 3) & 07;

    // See scs.incl.pl1

    if (config_switches [scu_unit_idx].mode != MODE_PROGRAM)
      {
        sim_warn ("%s: SCU mode is 'MANUAL', not 'PROGRAM' -- sscr "
                   "not allowed to set switches.\n",
                   __func__);
// XXX [CAC] Setting an unassigned register generates a STORE FAULT;
// this probably should as well
        return SCPE_OK;
      }

// Not used by 4MW

    switch (function)
      {
        case 00000: // Set system controller mode register
          {
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            scu [scu_unit_idx].id = (word4) getbits36_4 (regq, 50 - 36);
            scu [scu_unit_idx].mode_reg = getbits36_18 (regq, 54 - 36);
#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
          }
          break;

        case 00001: // Set system controller configuration register
                    // (4MW SCU only)
          {
            sim_debug (DBG_DEBUG, & scu_dev,
                       "sscr 1 %d A: %012"PRIo64" Q: %012"PRIo64"\n",
                       scu_unit_idx, rega, regq);
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            scu_t * up = scu + scu_unit_idx;
            for (int maskab = 0; maskab < 2; maskab ++)
              {
                word9 mask = ((maskab ? regq : rega) >> 27) & 0777;
                if (mask & 01)
                  {
                    up -> mask_enable [maskab] = 0;
                    sim_debug (DBG_DEBUG, & scu_dev,
                               "sscr %u mask disable  %d\n",
                               scu_unit_idx, maskab);
                  }
                else
                  {
                    up -> mask_enable [maskab] = 1;
                    sim_debug (DBG_DEBUG, & scu_dev,
                               "sscr %u mask enable  %d\n",
                               scu_unit_idx, maskab);
                    for (int pn = 0; pn < N_SCU_PORTS; pn ++)
                      {
                        if ((2 << (N_SCU_PORTS - 1 - pn)) & mask)
                          {
                            up -> mask_assignment [maskab] = (uint) pn;
                            break;
                          }
                      }

                  }
                sim_debug (DBG_INTR, & scu_dev,
                           "SCU%u SSCR1 mask %c enable set to %u assigned to "
                           "port %u\n",
                           scu_unit_idx, 'a' + maskab, up->mask_enable[maskab],
                           up->mask_assignment[maskab]);
              }
            // AN87-00A, pg 2-5, 2-6 specify which fields are and are not
            //  settable.

            //if (up -> lower_store_size != ((rega >> 24) & 07))
              //sim_printf ("??? The CPU tried to change the SCU store size\n");
            up -> lower_store_size = (rega >> 24) & 07;
            up -> cyclic           = (regq >>  8) & 0177;
            up -> nea              = (rega >>  6) & 0377;
            up -> onl              = (rega >> 20) & 017;
            up -> interlace        = (rega >>  5) &  1;
            up -> lwr              = (rega >>  4) &  1;
            up -> port_enable [0]  = (rega >>  3) & 01;
            up -> port_enable [1]  = (rega >>  2) & 01;
            up -> port_enable [2]  = (rega >>  1) & 01;
            up -> port_enable [3]  = (rega >>  0) & 01;
            up -> port_enable [4]  = (regq >>  3) & 01;
            up -> port_enable [5]  = (regq >>  2) & 01;
            up -> port_enable [6]  = (regq >>  1) & 01;
            up -> port_enable [7]  = (regq >>  0) & 01;

#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
            // XXX A, A1, B, B1, INT, LWR not implemented. (AG87-00A pgs 2-5,
            //  2-6)
            break;
          }

        case 00002: // Set mask register port 0
        //case 00012: // Set mask register port 1
        //case 00022: // Set mask register port 2
        //case 00032: // Set mask register port 3
        //case 00042: // Set mask register port 4
        //case 00052: // Set mask register port 5
        //case 00062: // Set mask register port 6
        //case 00072: // Set mask register port 7
          {
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            uint port_num = (addr >> 6) & 07;
            sim_debug (DBG_DEBUG, & scu_dev, "Set mask register port %d to "
                       "%012"PRIo64",%012"PRIo64"\n",
                       port_num, rega, regq);

            // Find mask reg assigned to specified port
            int mask_num = -1;
            uint n_masks_found = 0;
            for (int p = 0; p < N_ASSIGNMENTS; p ++)
              {
                //if (scup -> interrupts [p].mask_assign.unassigned)
                if (scu [scu_unit_idx].mask_enable [p] == 0)
                  continue;
                //if (scup -> interrupts [p].mask_assign.port == port_num)
                if (scu [scu_unit_idx ].mask_assignment [p] == port_num)
                  {
                    if (n_masks_found == 0)
                      mask_num = p;
                    n_masks_found ++;
                  }
              }

            if (! n_masks_found)
              {
// According to bootload_tape_label.alm, this condition is OK
                sim_debug (DBG_WARN, & scu_dev,
                           "%s: No masks assigned to cpu on port %d\n",
                           __func__, port_num);
#if defined(THREADZ) || defined(LOCKLESS)
                unlock_scu ();
#endif
                return SCPE_OK;
              }

            if (n_masks_found > 1)
              {
                // Not legal for Multics
                sim_debug (DBG_WARN, & scu_dev,
                           "%s: Multiple masks assigned to cpu on port %d\n",
                           __func__, port_num);
              }

            // See AN87
            //scup -> interrupts[mask_num].exec_intr_mask = 0;
            scu [scu_unit_idx].exec_intr_mask [mask_num] = 0;
            scu [scu_unit_idx].exec_intr_mask [mask_num] |=
              ((word32) getbits36_16(rega, 0) << 16);
            scu [scu_unit_idx].exec_intr_mask [mask_num] |=
              getbits36_16(regq, 0);
#if 0
            sim_debug (DBG_DEBUG, & scu_dev,
                       "%s: PIMA %c: EI mask set to %s\n",
                       __func__, mask_num + 'A',
                       bin2text(scu [scu_unit_idx].exec_intr_mask [mask_num],
                       N_CELL_INTERRUPTS));
            //sim_printf ("sscr  exec_intr_mask %012o\n",
                          //scu [scu_unit_idx].exec_intr_mask [mask_num]);
#endif
            sim_debug (DBG_TRACE, & scu_dev,
                       "SSCR Set mask unit %u port %u mask_num %u "
                       "mask 0x%08x\n",
                       scu_unit_idx, port_num, mask_num,
                       scu [scu_unit_idx].exec_intr_mask [mask_num]);
            dump_intr_regs ("sscr set mask", scu_unit_idx);
            scu [scu_unit_idx].mask_enable [mask_num] = 1;
            sim_debug (DBG_INTR, & scu_dev,
                       "SCU%u SSCR2 exec_intr mask %c set to 0x%08x"
                       " and enabled.\n",
                       scu_unit_idx, 'a' + mask_num,
                       scu[scu_unit_idx].exec_intr_mask[mask_num]);

            deliver_interrupts (scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
          }
          break;

        case 00003: // Set interrupt cells
          {
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            for (uint i = 0; i < 16; i ++)
              {
                scu [scu_unit_idx].cells [i] =
                  getbits36_1 (rega, i) ? 1 : 0;
                scu [scu_unit_idx].cells [i + 16] =
                  getbits36_1 (regq, i) ? 1 : 0;
              }
            char pcellb [N_CELL_INTERRUPTS + 1];
            sim_debug (DBG_TRACE, & scu_dev,
                       "SSCR Set int. cells: Unit %u Cells: %s\n",
                       scu_unit_idx, pcells (scu_unit_idx, pcellb));
            sim_debug (DBG_INTR, & scu_dev,
                       "SCU%u SSCR3  Set int. cells %s\n",
                       scu_unit_idx, pcells (scu_unit_idx, pcellb));
            dump_intr_regs ("sscr set interrupt cells", scu_unit_idx);
            deliver_interrupts (scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
          }
          break;

        case 00004: // Set calendar clock (4MW SCU only)
        case 00005:
          {
            // AQ: 20-35 clock bits 0-15, 36-71 clock bits 16-51
            word16 b0_15   = (word16) getbits36_16 (cpu.rA, 20);
            word36 b16_51  = cpu.rQ;
            uint64 new_clk = (((uint64) b0_15) << 36) | b16_51;
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            scu [scu_unit_idx].user_correction =
              (int64) (new_clk - set_SCU_clock (scu_unit_idx));
#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
            //sim_printf ("sscr %o\n", function);
          }
          break;

        case 00006: // Set unit mode register
        case 00007:
          // XXX See notes in AL39 sscr re: store unit selection
          //sim_printf ("sscr %o\n", function);
          sim_warn ("sscr set unit mode register\n");
          //return STOP_UNIMP;
          return SCPE_OK;

        default:
          sim_warn ("sscr unhandled code\n");
          //return STOP_UNIMP;
          return SCPE_OK;
          //sim_printf ("sscr %o\n", function);
      }
    return SCPE_OK;
  }

t_stat scu_rscr (uint scu_unit_idx, uint cpu_unit_udx, word18 addr,
                 word36 * rega, word36 * regq)
  {
    // Only valid for a 4MW SCU

    if (scu_unit_idx >= scu_dev.numunits)
      {
        sim_warn ("%s: scu_unit_idx out of range %d\n",
                   __func__, scu_unit_idx);
        return SCPE_OK;
      }

    // BCE uses clever addressing schemes to select SCUs; it appears we need
    // to be more selecting in picking out the function bits;
    //uint function = (addr >> 3) & 07777;
    uint function = (addr >> 3) & 07;

    //sim_printf ("rscr %o\n", function);

    // See scs.incl.pl1

    switch (function)
      {
        case 00000: // Read system controller mode register
          {
            // AN-87
            // 0..0 -> A
            // 0..0 -> Q 36-49 (0-13)
            // ID -> Q 50-53 (14-17)
            // MODE REG -> Q 54-71 (18-35)
            //
            //  ID: 0000  8034, 8035
            //      0001  Level 68 SC
            //      0010  Level 66 SCU
            // CAC: According to scr.incl.pl1. 0010 is a 4MW SCU
            // MODE REG: these fields are only used by T&D
            * rega = 0;
            //* regq = 0000002000000; // ID = 0010
            * regq = 0;
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            putbits36_4 (regq, 50 - 36, scu [scu_unit_idx].id);
            putbits36_18 (regq, 54 - 36, scu [scu_unit_idx].mode_reg);
#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
            break;
          }

        case 00001: // Read system controller configuration register
          {
            // AN-87, scr.incl.pl1
            //
            // SCU:
            // reg A:
            //   MASK A | SIZE | A | A1 | B | B1 | PORT | 0 | MOD | NEA |
            //   INT | LWR | PMR 0-3
            // reg Q:
            //   MASK B | not used | CYCLIC PRIOR | not used | PMR 4-7
            //
            //   MASK A/B (9 bits): EIMA switch setting for mask A/B. The
            //    assigned port corresponds to the but position within the
            //    field. A bit in position 9 indicates that the mask is
            //    not assigned.
            // From scr.incl.pl1:
            // 400 => assigned to port 0
            //  .
            //  .
            // 002 => assigned to port 7
            // 001 => mask off */

            //
            //  SIZE (3 bits): Size of lower store
            //    000 = 32K ... 111 = 4M
            //
            //  A A1 B B1 (1 bit): store unit A/A1/B/B1 online
            //
            //  PORT (4 bits): Port number of the SCU port through which
            //    the RSCR instruction was received
            //
            //struct config_switches * sw = config_switches + scu_unit_idx;
            sim_debug (DBG_DEBUG, & scu_dev, "rscr 1 %d\n", scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            scu_t * up = scu + scu_unit_idx;
            word9 maskab [2];
            for (int i = 0; i < 2; i ++)
              {
                if (up -> mask_enable [i])
                  {
                    maskab [i] = (2 << (N_SCU_PORTS - 1 -
                                        up -> mask_assignment [i])) & 0777;
                  }
                else
                  maskab [i] = 0001;
              }

            int scu_port_num = -1; // The port that the rscr instruction was
                                   // received on

            for (int pn = 0; pn < N_SCU_PORTS; pn ++)
              {
                for (int sn = 0; sn < N_SCU_SUBPORTS; sn ++)
                  {
                    if (cables->scu_to_cpu[scu_unit_idx][pn][sn].in_use &&
                        cables->scu_to_cpu[scu_unit_idx][pn][sn].cpu_unit_idx ==
                          cpu_unit_udx)
                     {
                        scu_port_num = pn;
                        goto gotit;
                      }
                  }
              }
gotit:;
            if (scu_port_num < 0)
              {
#if defined(THREADZ) || defined(LOCKLESS)
                unlock_scu ();
#endif
                sim_warn ("%s: can't find cpu port in the snarl of cables; "
                           "scu_unit_no %d, cpu_unit_udx %d\n",
                           __func__, scu_unit_idx, cpu_unit_udx);
                return SCPE_OK;
              }

            // AN87, pg 2-5
            word36 a, q;

            a = 0;
// (data, starting bit position, number of bits, value)
            putbits36_9 (& a,  0,  maskab [0]);
            putbits36_3 (& a,  9,  (word3) up -> lower_store_size);
            putbits36_4 (& a, 12,  (word4) up -> onl); // A, A1, B, B1 online
            putbits36_4 (& a, 16,  (word4) scu_port_num);
            putbits36_1 (& a, 21,  (word1) config_switches[scu_unit_idx].mode);
            putbits36_8 (& a, 22,  (word8) up -> nea);
            putbits36_1 (& a, 30,  (word1) up -> interlace);
            putbits36_1 (& a, 31,  (word1) up -> lwr);
            // XXX INT, LWR not implemented. (AG87-00A pgs 2-5. 2-6)
            // interlace <- 0
            // lower <- 0
            // Looking at scr_util.list, I *think* the port order
            // 0,1,2,3.
            putbits36_1 (& a, 32,  (word1) up -> port_enable [0]);
            putbits36_1 (& a, 33,  (word1) up -> port_enable [1]);
            putbits36_1 (& a, 34,  (word1) up -> port_enable [2]);
            putbits36_1 (& a, 35,  (word1) up -> port_enable [3]);
            * rega = a;

            q = 0;
            putbits36_9 (& q,  0,  maskab [1]);
            // cyclic prior <- 0
            putbits36_7 (& q, 57-36, (word7) up -> cyclic & MASK7);
            // Looking at scr_util.list, I *think* the port order
            // 0,1,2,3.
            putbits36_1 (& q, 32,  (word1) up -> port_enable [4]);
            putbits36_1 (& q, 33,  (word1) up -> port_enable [5]);
            putbits36_1 (& q, 34,  (word1) up -> port_enable [6]);
            putbits36_1 (& q, 35,  (word1) up -> port_enable [7]);
            * regq = q;

#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
            sim_debug (DBG_DEBUG, & scu_dev,
                       "rscr 1 %d A: %012"PRIo64" Q: %012"PRIo64"\n",
                       scu_unit_idx, * rega, * regq);
            break;
          }

        case 00002: // mask register
          {
            uint port_num = (addr >> 6) & MASK3;
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            scu_t * up = scu + scu_unit_idx;
            uint mask_contents = 0;
            if (up -> mask_assignment [0] == port_num)
              {
                mask_contents = up -> exec_intr_mask [0];
              }
            else if (up -> mask_assignment [1] == port_num)
              {
                mask_contents = up -> exec_intr_mask [1];
              }
            mask_contents &= MASK32;

            * rega = 0;
            putbits36 (rega,  0, 16, (mask_contents >> 16) & MASK16);
            putbits36 (rega, 32,  1, up -> port_enable [0]);
            putbits36 (rega, 33,  1, up -> port_enable [1]);
            putbits36 (rega, 34,  1, up -> port_enable [2]);
            putbits36 (rega, 35,  1, up -> port_enable [3]);

            * regq = 0;
            putbits36 (rega,  0, 16, (mask_contents >>  0) & MASK16);
            putbits36 (regq, 32,  1, up -> port_enable [4]);
            putbits36 (regq, 33,  1, up -> port_enable [5]);
            putbits36 (regq, 34,  1, up -> port_enable [6]);
            putbits36 (regq, 35,  1, up -> port_enable [7]);

#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
            sim_debug (DBG_TRACE, & scu_dev,
                       "RSCR mask unit %u port %u assigns %u %u mask 0x%08x\n",
                       scu_unit_idx, port_num, up -> mask_assignment [0],
                       up -> mask_assignment [1],
                       mask_contents);
          }
          break;

        case 00003: // Interrupt cells
          {
#if defined(THREADZ) || defined(LOCKLESS)
            lock_scu ();
#endif
            scu_t * up = scu + scu_unit_idx;
            // * rega = up -> exec_intr_mask [0];
            // * regq = up -> exec_intr_mask [1];
            for (uint i = 0; i < N_CELL_INTERRUPTS; i ++)
              {
                word1 cell = up -> cells [i] ? 1 : 0;
                if (i < 16)
                  putbits36_1 (rega, i, cell);
                else
                  putbits36_1 (regq, i - 16, cell);
              }
#if defined(THREADZ) || defined(LOCKLESS)
            unlock_scu ();
#endif
          }
          break;

        case 00004: // Get calendar clock (4MW SCU only)
        case 00005:
          {
            uint64 clk = set_SCU_clock (scu_unit_idx);
            cpu.rQ =  clk  & 0777777777777;    // lower 36-bits of clock
            cpu.rA = (clk >> 36) & 0177777;    // upper 16-bits of clock
#ifdef TESTING
            HDBGRegAW ("rscr get clock");
            HDBGRegQW ("rscr get clock");
#endif
          }
        break;

        case 00006: // SU Mode register
        case 00007: // SU Mode register
          {
            //sim_printf ("rscr SU Mode Register%o\n", function);

// Completely undocumented...
//   scr.incl.alm
//"         Structure scr_su
//"
//          equ       scr_su_size,2
//
//
//          equ       scr_su.ZAC_line_word,1
//          equ       scr_su.ZAC_line_shift,30
//          bool      scr_su.ZAC_line_mask,000077
//          equ       scr_su.syndrome_word,1
//          equ       scr_su.syndrome_shift,22
//          bool      scr_su.syndrome_mask,000377
//          equ       scr_su.identification_word,1
//          equ       scr_su.identification_shift,18
//          bool      scr_su.identification_mask,000017
//          equ       scr_su.EDAC_disabled_word,1
//          bool      scr_su.EDAC_disabled,400000   " DL
//          equ       scr_su.MINUS_5_VOLT_margin_word,1
//"         equ       scr_su.MINUS_5_VOLT_margin_shift,11
//          bool      scr_su.MINUS_5_VOLT_margin_mask,000003
//          equ       scr_su.PLUS_5_VOLT_margin_word,1
//          equ       scr_su.PLUS_5_VOLT_margin_shift,9
//          bool      scr_su.PLUS_5_VOLT_margin_mask,000003
//          equ       scr_su.spare_margin_word,1
//          equ       scr_su.spare_margin_shift,7
//          bool      scr_su.spare_margin_mask,000003
//          equ       scr_su.PLUS_19_VOLT_margin_word,1
//"         equ       scr_su.PLUS_19_VOLT_margin_shift,5
//          bool      scr_su.PLUS_19_VOLT_margin_mask,000003
//          equ       scr_su.SENSE_strobe_margin_word,1
//"         equ       scr_su.SENSE_strobe_margin_shift,2
//          bool      scr_su.SENSE_strobe_margin_mask,000003
//"         equ       scr_su.maint_functions_enabled_word,1
//          bool      scr_su.maint_functions_enabled,000001 " DL

//                 1   1      1   2    2    2       2     3   3        3  3
//   0     6       4   8      9   3    5    7       9     1   2        4  5
//  ------------------------------------------------------------------------------
//  | ZAC | synd | id | EDAC | 0 | -5 | +5 | spare | +19 | 0 | sense | 0 | maint |
//  ------------------------------------------------------------------------------
//       6      8    4      1   4    2    2      2      2   1       2   1       1

// Okay, it looks safe to return 0.

            * rega = 0;
            * regq = 0;
          }
          break;

        default:
          sim_warn ("rscr %o\n", function);
          return SCPE_OK;
      }
    return SCPE_OK;
  }

#if 0
struct timespec cioc_t0;
#endif

int scu_cioc (uint cpu_unit_udx, uint scu_unit_idx, uint scu_port_num,
              uint expander_command, uint sub_mask)
  {
#if 0
clock_gettime (CLOCK_REALTIME, & cioc_t0);
#endif
    sim_debug (DBG_DEBUG, & scu_dev,
               "scu_cioc: Connect from %o sent to "
               "unit %o port %o exp %o mask %03o\n",
               cpu_unit_udx, scu_unit_idx, scu_port_num,
              expander_command, sub_mask);

#if defined(THREADZ) || defined(LOCKLESS)
    lock_scu ();
#endif
    struct ports * portp = & scu [scu_unit_idx].ports [scu_port_num];

    int rc = 0;
    if (! scu [scu_unit_idx].port_enable [scu_port_num])
      {
        sim_debug (DBG_ERR, & scu_dev,
                   "scu_cioc: Connect sent to disabled port; dropping\n");
        sim_debug (DBG_ERR, & scu_dev,
                   "scu_cioc: scu_unit_idx %u scu_port_num %u\n",
                   scu_unit_idx, scu_port_num);
        rc = 1;
        goto done;
      }

    if (expander_command == 1) // "set subport enables"
      {
        for (uint i = 0; i < N_SCU_SUBPORTS; i++)
          {
            portp->subport_enables [i] = !! (sub_mask & (0200u >> i));
          }
        goto done;
      }

    if (expander_command == 2) // "set xipmask"
      {
        int cnt = 0;
        int val = -1;
        for (uint i = 0; i < N_SCU_SUBPORTS; i++)
          {
            portp->xipmask [i] = !! (sub_mask & (0200u >> i));
            if (portp->xipmask [i])
              {
                val = (int) i;
                cnt ++;
              }
          }
        if (cnt > 1)
          {
            sim_warn ("xip mask cnt > 1\n");
            val = -1;
          }
        portp->xipmaskval = val;
        goto done;
      }

    if (portp -> type == ADEV_IOM)
      {
        int iom_unit_idx = portp->dev_idx;
#if defined(THREADZ) || defined(LOCKLESS)
        unlock_scu ();
# if !defined(IO_ASYNC_PAYLOAD_CHAN) && !defined(IO_ASYNC_PAYLOAD_CHAN_THREAD)
        lock_iom ();
        lock_libuv ();
# endif
        iom_interrupt (scu_unit_idx, (uint) iom_unit_idx);
# if !defined(IO_ASYNC_PAYLOAD_CHAN) && !defined(IO_ASYNC_PAYLOAD_CHAN_THREAD)
        unlock_libuv ();
        unlock_iom ();
# endif
        return 0;
#else // ! THREADZ
        if (sys_opts.iom_times.connect <= 0)
          {
            iom_interrupt (scu_unit_idx, (uint) iom_unit_idx);
            goto done;
          }
        else
          {
//sim_printf ("scu_cioc: Queuing an IOM in %d cycles "
//"(for the connect channel) %u %d\n",
//sys_opts.iom_times.connect, scu_unit_idx, iom_unit_idx);
            sim_debug (DBG_INFO, & scu_dev,
                       "scu_cioc: Queuing an IOM in %d cycles "
                       "(for the connect channel)\n",
                       sys_opts.iom_times.connect);
            // Stash the iom_interrupt call parameters
            iom_dev.units[iom_unit_idx].u3 = (int32) scu_unit_idx;
            iom_dev.units[iom_unit_idx].u4 = (int32) iom_unit_idx;
            int rc;
            if ((rc = sim_activate (& iom_dev.units [iom_unit_idx],
                sys_opts.iom_times.connect)) != SCPE_OK)
              {
                sim_warn ("sim_activate failed (%d)\n", rc);
                goto done;
              }
            goto done;
          }
#endif // ! THREADZ
      }
    else if (portp -> type == ADEV_CPU)
      {

#if 1
// by subport_enables
        if (portp->is_exp)
          {
            for (uint sn = 0; sn < N_SCU_SUBPORTS; sn ++)
              {
                if (portp->subport_enables[sn])
                  {
                    if (! cables->
                            scu_to_cpu[scu_unit_idx][scu_port_num][sn].in_use)
                      {
                        sim_warn ("Can't find CPU to interrupt\n");
                        continue;
                      }
                    uint cpu_unit_udx = cables->
                      scu_to_cpu[scu_unit_idx][scu_port_num][sn].cpu_unit_idx;
                    setG7fault ((uint) cpu_unit_udx, FAULT_CON, fst_zero);
                  }
              }
          }
        else
          {
            if (! cables->scu_to_cpu[scu_unit_idx][scu_port_num][0].in_use)
              {
                sim_warn ("Can't find CPU to interrupt\n");
                rc = 1;
                goto done;
              }
            uint cpu_unit_udx =
              cables->scu_to_cpu[scu_unit_idx][scu_port_num][0].cpu_unit_idx;
            setG7fault ((uint) cpu_unit_udx, FAULT_CON, fst_zero);
          }
#else
// by xipmaskval
        int cpu_unit_udx = -1;
        if (portp->is_exp)
          {
            cpu_unit_udx =
              cables->cablesFromCpus[scu_unit_idx][scu_port_num]
                [portp->xipmaskval].cpu_unit_udx;
          }
        else
          {
            cpu_unit_udx = cables ->cablesFromCpus[scu_unit_idx]
              [scu_port_num][0].cpu_unit_udx;
          }
        if (cpu_unit_udx < 0)
          {
            sim_warn ("Can't find CPU to interrupt\n");
            rc = 1;
            goto done;
          }
        setG7fault (cpu_unit_udx, FAULT_CON, (_fault_subtype) {.bits=0});
#endif
        goto done;
      }
    else
      {
        sim_debug (DBG_ERR, & scu_dev,
                   "scu_cioc: Connect sent to not-an-IOM or CPU; dropping\n");
        rc = 1;
        goto done;
      }
done:
#if defined(THREADZ) || defined(LOCKLESS)
    unlock_scu ();
#endif
    return rc;
}

// =============================================================================

// The SXC (set execute cells) SCU command.

// From AN70:
//  It then generates a word with
// the <interrupt number>th bit set and sends this to the bootload
// SCU with the SC (set execute cells) SCU command.
//

int scu_set_interrupt (uint scu_unit_idx, uint inum)
  {
    const char* moi = "SCU::interrupt";

    if (inum >= N_CELL_INTERRUPTS)
      {
        sim_debug (DBG_WARN, & scu_dev,
                   "%s: Bad interrupt number %d\n", moi, inum);
        return 1;
      }

#if defined(THREADZ) || defined(LOCKLESS)
    lock_scu ();
#endif
    scu [scu_unit_idx].cells [inum] = 1;
    dump_intr_regs ("scu_set_interrupt", scu_unit_idx);
    deliver_interrupts (scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
    unlock_scu ();
#endif
    return 0;
}

// Scan a SCU for interrupts from highest to lowest. If an interrupt is
// present, clear it, update the interrupt state bits and return the fault
// pair address for the interrupt (2 * interrupt number). If no interrupt
// is present, return 1.
//

uint scu_get_highest_intr (uint scu_unit_idx)
  {
#if defined(THREADZ) || defined(LOCKLESS)
    lock_scu ();
#endif
    // lower numbered cells have higher priority
    for (int inum = 0; inum < N_CELL_INTERRUPTS; inum ++)
      {
        for (uint pima = 0; pima < N_ASSIGNMENTS; pima ++) // A, B
          {
            if (scu [scu_unit_idx].mask_enable [pima] == 0)
              continue;
            uint mask = scu [scu_unit_idx].exec_intr_mask [pima];
            uint port = scu [scu_unit_idx].mask_assignment [pima];
//            if (scu [scu_unit_idx].ports [port].type != ADEV_CPU ||
//              scu [scu_unit_idx].ports [port].dev_idx != current_running_cpu_idx)
            if (scu[scu_unit_idx].ports[port].type != ADEV_CPU ||
                cpus[current_running_cpu_idx].scu_port[scu_unit_idx] != port)
              continue;
            if (scu [scu_unit_idx].cells [inum] &&
                (mask & (1u << (31 - inum))) != 0)
              {
                sim_debug (DBG_TRACE, & scu_dev, "scu_get_highest_intr inum %d pima %u mask 0%011o port %u cells 0%011o\n", inum, pima, mask, port, scu [scu_unit_idx].cells [inum]);
                scu [scu_unit_idx].cells [inum] = false;
                dump_intr_regs ("scu_get_highest_intr", scu_unit_idx);
                deliver_interrupts (scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
                unlock_scu ();
#endif
                return (uint) inum * 2;
              }
          }
      }
#if defined(THREADZ) || defined(LOCKLESS)
    unlock_scu ();
#endif
    return 1;
  }

t_stat scu_reset_unit (UNIT * uptr, UNUSED int32 value,
                       UNUSED const char * cptr,
                       UNUSED void * desc)
  {
    uint scu_unit_idx = (uint) (uptr - scu_unit);
    scu_unit_reset ((int) scu_unit_idx);
    return SCPE_OK;
  }

void scu_init (void)
  {
    // One time only initializations

    for (int u = 0; u < N_SCU_UNITS_MAX; u ++)
      {
        for (int p = 0; p < N_SCU_PORTS; p ++)
          {
            for (int s = 0; s < N_SCU_SUBPORTS; s ++)
              {
                scu[u].ports[p].dev_port[s]        = -1;
                scu[u].ports[p].subport_enables[s] = false;
                scu[u].ports[p].xipmask[s]         = false;
                // Invalid value for detecting uninitialized XIP mask.
                scu[u].ports[p].xipmaskval         = N_SCU_SUBPORTS;
              }
            scu[u].ports[p].type   = ADEV_NONE;
            scu[u].ports[p].is_exp = false;
          }

        //  ID: 0000  8034, 8035
        //      0001  Level 68 SC
        //      0010  Level 66 SCU
        scu [u].id           = 02l; // 0b0010
        scu [u].mode_reg     = 0;   // used by T&D
        scu [u].elapsed_days = 0;
      }

  }

t_stat scu_rmcm (uint scu_unit_idx, uint cpu_unit_udx, word36 * rega,
                 word36 * regq)
  {
    scu_t * up = scu + scu_unit_idx;

    // Assume no mask register assigned
    * rega = 0;
    * regq = 0;

    // Which port is cpu_unit_udx connected to? (i.e. which port did the
    // command come in on?
    int scu_port_num = -1; // The port that the rscr instruction was
                           // received on

    for (int pn = 0; pn < N_SCU_PORTS; pn ++)
      {
        for (int sn = 0; sn < N_SCU_SUBPORTS; sn ++)
          {
            if (cables->scu_to_cpu[scu_unit_idx][pn][sn].in_use &&
                cables->scu_to_cpu[scu_unit_idx][pn][sn].cpu_unit_idx ==
                  cpu_unit_udx)
              {
                scu_port_num = pn;
                goto gotit;
              }
          }
      }

gotit:;

    //sim_printf ("rmcm scu_port_num %d\n", scu_port_num);

    if (scu_port_num < 0)
      {
        sim_warn ("%s: can't find cpu port in the snarl of cables; "
                  "scu_unit_no %d, cpu_unit_udx %d\n",
                  __func__, scu_unit_idx, cpu_unit_udx);
        sim_debug (DBG_ERR, & scu_dev,
                   "%s: can't find cpu port in the snarl of cables; "
                   "scu_unit_no %d, cpu_unit_udx %d\n",
                   __func__, scu_unit_idx, cpu_unit_udx);
        // Non 4MWs do a store fault
        return SCPE_OK;
      }

    // A reg:
    //  0          15  16           31  32       35
    //    IER 0-15        00000000        PER 0-3
    // Q reg:
    //  0          15  16           31  32       35
    //    IER 16-32       00000000        PER 4-7

    sim_debug (DBG_TRACE, & scu_dev, "rmcm selected scu port %u\n",
               scu_port_num);
#if defined(THREADZ) || defined(LOCKLESS)
    lock_scu ();
#endif
    uint mask_contents = 0;
    if (up -> mask_assignment [0] == (uint) scu_port_num)
      {
        mask_contents = up -> exec_intr_mask [0];
        sim_debug (DBG_TRACE, & scu_dev, "rmcm got mask %011o from pima A\n",
                   mask_contents);
      }
    else if (up -> mask_assignment [1] == (uint) scu_port_num)
      {
        mask_contents = up -> exec_intr_mask [1];
        sim_debug (DBG_TRACE, & scu_dev, "rmcm got mask %011o from pima B\n",
                   mask_contents);
      }
    mask_contents &= MASK32;

    * rega = 0;  //-V1048
    putbits36_16 (rega,  0, (mask_contents >> 16) & MASK16);
    putbits36_1  (rega, 32,  (word1) up -> port_enable [0]);
    putbits36_1  (rega, 33,  (word1) up -> port_enable [1]);
    putbits36_1  (rega, 34,  (word1) up -> port_enable [2]);
    putbits36_1  (rega, 35,  (word1) up -> port_enable [3]);

    * regq = 0;  //-V1048
    putbits36_16 (regq,  0, (mask_contents >>  0) & MASK16);
    putbits36_1  (regq, 32,  (word1) up -> port_enable [4]);
    putbits36_1  (regq, 33,  (word1) up -> port_enable [5]);
    putbits36_1  (regq, 34,  (word1) up -> port_enable [6]);
    putbits36_1  (regq, 35,  (word1) up -> port_enable [7]);

#if defined(THREADZ) || defined(LOCKLESS)
    unlock_scu ();
#endif
    sim_debug (DBG_TRACE, & scu_dev,
               "RMCM returns %012"PRIo64" %012"PRIo64"\n",
               * rega, * regq);
    dump_intr_regs ("rmcm", scu_unit_idx);
    return SCPE_OK;
  }

t_stat scu_smcm (uint scu_unit_idx, uint cpu_unit_udx, word36 rega, word36 regq)
  {
    sim_debug (DBG_TRACE, & scu_dev,
              "SMCM SCU unit %d CPU unit %d A %012"PRIo64" Q %012"PRIo64"\n",
               scu_unit_idx, cpu_unit_udx, rega, regq);

    scu_t * up = scu + scu_unit_idx;

    // Which port is cpu_unit_udx connected to? (i.e. which port did the
    // command come in on?
    int scu_port_num = -1; // The port that the rscr instruction was
                           // received on

    for (int pn = 0; pn < N_SCU_PORTS; pn ++)
      {
        for (int sn = 0; sn < N_SCU_SUBPORTS; sn ++)
          {
            if (cables->scu_to_cpu[scu_unit_idx][pn][sn].in_use &&
                cables->scu_to_cpu[scu_unit_idx][pn][sn].cpu_unit_idx ==
                  cpu_unit_udx)
              {
                scu_port_num = pn;
                goto gotit;
              }
          }
      }
gotit:;

    //sim_printf ("rmcm scu_port_num %d\n", scu_port_num);

    if (scu_port_num < 0)
      {
        sim_warn ("%s: can't find cpu port in the snarl of cables; "
                   "scu_unit_no %d, cpu_unit_udx %d\n",
                   __func__, scu_unit_idx, cpu_unit_udx);
        return SCPE_OK;
      }

    sim_debug (DBG_TRACE, & scu_dev, "SMCM SCU port num %d\n", scu_port_num);

    // A reg:
    //  0          15  16           31  32       35
    //    IER 0-15        00000000        PER 0-3
    // Q reg:
    //  0          15  16           31  32       35
    //    IER 16-32       00000000        PER 4-7

    uint imask =
      ((uint) getbits36_16(rega, 0) << 16) |
      ((uint) getbits36_16(regq, 0) <<  0);
#if defined(THREADZ) || defined(LOCKLESS)
    lock_scu ();
#endif
    if (up -> mask_assignment [0] == (uint) scu_port_num)
      {
        up -> exec_intr_mask [0] = imask;
        sim_debug (DBG_TRACE, & scu_dev, "SMCM intr mask 0 set to %011o\n",
                   imask);
      }
    else if (up -> mask_assignment [1] == (uint) scu_port_num)
      {
        up -> exec_intr_mask [1] = imask;
        sim_debug (DBG_TRACE, & scu_dev, "SMCM intr mask 1 set to %011o\n",
                   imask);
      }

    scu [scu_unit_idx].port_enable [0] = (uint) getbits36_1 (rega, 32);
    scu [scu_unit_idx].port_enable [1] = (uint) getbits36_1 (rega, 33);
    scu [scu_unit_idx].port_enable [2] = (uint) getbits36_1 (rega, 34);
    scu [scu_unit_idx].port_enable [3] = (uint) getbits36_1 (rega, 35);
    scu [scu_unit_idx].port_enable [4] = (uint) getbits36_1 (regq, 32);
    scu [scu_unit_idx].port_enable [5] = (uint) getbits36_1 (regq, 33);
    scu [scu_unit_idx].port_enable [6] = (uint) getbits36_1 (regq, 34);
    scu [scu_unit_idx].port_enable [7] = (uint) getbits36_1 (regq, 35);

    dump_intr_regs ("smcm", scu_unit_idx);
    deliver_interrupts (scu_unit_idx);
#if defined(THREADZ) || defined(LOCKLESS)
    unlock_scu ();
#endif

    return SCPE_OK;
  }
