root/src/dps8/dps8_console.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ta_flush
  2. ta_push
  3. ta_peek
  4. ta_get
  5. opc_reset
  6. check_attn_key
  7. console_init
  8. console_exit
  9. opc_autoinput_set
  10. clear_opc_autoinput
  11. add_opc_autoinput
  12. opc_autoinput_show
  13. console_attn
  14. console_attn_idx
  15. newlineOff
  16. newlineOn
  17. handleRCP
  18. sendConsole
  19. consoleProcessIdx
  20. consoleProcess
  21. opc_iom_cmd
  22. opc_svc
  23. opc_show_nunits
  24. opc_set_nunits
  25. opc_set_config
  26. opc_show_config
  27. opc_show_device_name
  28. opc_set_device_name
  29. opc_set_console_port
  30. opc_show_console_port
  31. opc_set_console_address
  32. opc_show_console_address
  33. opc_set_console_pw
  34. opc_show_console_pw
  35. console_putstr
  36. consolePutchar0
  37. console_putchar
  38. consoleConnectPrompt
  39. startRemoteConsole

   1 /*
   2  * vim: filetype=c:tabstop=4:ai:expandtab
   3  * SPDX-License-Identifier: ICU
   4  * SPDX-License-Identifier: Multics
   5  * scspell-id: 6421764a-f62d-11ec-b542-80ee73e9b8e7
   6  *
   7  * ---------------------------------------------------------------------------
   8  *
   9  * Copyright (c) 2007-2013 Michael Mondy
  10  * Copyright (c) 2012-2016 Harry Reed
  11  * Copyright (c) 2013-2016 Charles Anthony
  12  * Copyright (c) 2021-2025 The DPS8M Development Team
  13  *
  14  * This software is made available under the terms of the ICU License.
  15  * See the LICENSE.md file at the top-level directory of this distribution.
  16  *
  17  * ---------------------------------------------------------------------------
  18  *
  19  * This source file may contain code comments that adapt, include, and/or
  20  * incorporate Multics program code and/or documentation distributed under
  21  * the Multics License.  In the event of any discrepancy between code
  22  * comments herein and the original Multics materials, the original Multics
  23  * materials should be considered authoritative unless otherwise noted.
  24  * For more details and historical background, see the LICENSE.md file at
  25  * the top-level directory of this distribution.
  26  *
  27  * ---------------------------------------------------------------------------
  28  */
  29 
  30 #include <stdio.h>
  31 #include <unistd.h>
  32 #include <signal.h>
  33 #if !defined(__MINGW64__) && !defined(__MINGW32__)
  34 # include <termios.h>
  35 #endif /* if !defined(__MINGW64__) && !defined(__MINGW32__) */
  36 #include <ctype.h>
  37 
  38 #include "dps8.h"
  39 #include "dps8_iom.h"
  40 #include "dps8_sys.h"
  41 #include "dps8_console.h"
  42 #include "dps8_cable.h"
  43 #include "dps8_cpu.h"
  44 #include "dps8_faults.h"
  45 #include "dps8_scu.h"
  46 #include "dps8_mt.h"  // attachTape
  47 #include "dps8_disk.h"  // attachDisk
  48 #include "dps8_utils.h"
  49 #if defined(LOCKLESS)
  50 # include "threadz.h"
  51 #endif /* if defined(LOCKLESS) */
  52 
  53 #include "libtelnet.h"
  54 #if defined(CONSOLE_FIX)
  55 # include "threadz.h"
  56 #endif /* if defined(CONSOLE_FIX) */
  57 
  58 #if defined(SIM_NAME)
  59 # undef SIM_NAME
  60 #endif /* if defined(SIM_NAME) */
  61 #define SIM_NAME "DPS8M"
  62 
  63 #define DBG_CTR 1
  64 #define ASSUME0 0
  65 
  66 #if defined(FREE)
  67 # undef FREE
  68 #endif /* if defined(FREE) */
  69 #define FREE(p) do  \
  70   {                 \
  71     free((p));      \
  72     (p) = NULL;     \
  73   } while(0)
  74 
  75 // config switch -- The bootload console has a 30-second timer mechanism. When
  76 // reading from the console, if no character is typed within 30 seconds, the
  77 // read operation is terminated. The timer is controlled by an enable switch,
  78 // must be set to enabled during Multics and BCE
  79 
  80 static t_stat opc_reset (DEVICE * dptr);
  81 static t_stat opc_show_nunits (FILE *st, UNIT *uptr, int val,
  82                                const void *desc);
  83 static t_stat opc_set_nunits (UNIT * uptr, int32 value, const char * cptr,
  84                               void * desc);
  85 static t_stat opc_autoinput_set (UNIT *uptr, int32 val, const char *cptr,
  86                                  void *desc);
  87 static t_stat opc_autoinput_show (FILE *st, UNIT *uptr, int val,
  88                                   const void *desc);
  89 static t_stat opc_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
  90                               const char * cptr, UNUSED void * desc);
  91 static t_stat opc_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
  92                                UNUSED int  val, UNUSED const void * desc);
  93 static t_stat opc_set_console_port (UNIT * uptr, UNUSED int32 value,
  94                                     const char * cptr, UNUSED void * desc);
  95 static t_stat opc_show_console_port (UNUSED FILE * st, UNIT * uptr,
  96                                        UNUSED int val, UNUSED const void * desc);
  97 static t_stat opc_set_console_address (UNIT * uptr, UNUSED int32 value,
  98                                     const char * cptr, UNUSED void * desc);
  99 static t_stat opc_show_console_address (UNUSED FILE * st, UNIT * uptr,
 100                                        UNUSED int val, UNUSED const void * desc);
 101 static t_stat opc_set_console_pw (UNIT * uptr, UNUSED int32 value,
 102                                     const char * cptr, UNUSED void * desc);
 103 static t_stat opc_show_console_pw (UNUSED FILE * st, UNIT * uptr,
 104                                        UNUSED int val, UNUSED const void * desc);
 105 static t_stat opc_set_device_name (UNIT * uptr, UNUSED int32 value,
 106                                    const char * cptr, UNUSED void * desc);
 107 static t_stat opc_show_device_name (UNUSED FILE * st, UNIT * uptr,
 108                                     UNUSED int val, UNUSED const void * desc);
 109 
 110 static MTAB opc_mtab[] =
 111   {
 112     {
 113        MTAB_unit_nouc,     /* Mask          */
 114        0,                  /* Match         */
 115        "AUTOINPUT",        /* Print string  */
 116        "AUTOINPUT",        /* Match pstring */
 117        opc_autoinput_set,
 118        opc_autoinput_show,
 119        NULL,
 120        NULL
 121     },
 122 
 123     {
 124       MTAB_dev_valr,                        /* Mask               */
 125       0,                                    /* Match              */
 126       "NUNITS",                             /* Print string       */
 127       "NUNITS",                             /* Match string       */
 128       opc_set_nunits,                       /* Validation routine */
 129       opc_show_nunits,                      /* Display routine    */
 130       "Number of OPC units in the system",  /* Value descriptor   */
 131       NULL                                  /* Help               */
 132     },
 133 
 134     {
 135       MTAB_unit_uc,                   /* Mask               */
 136       0,                              /* Match              */
 137       (char *) "CONFIG",              /* Print string       */
 138       (char *) "CONFIG",              /* Match string       */
 139       opc_set_config,                 /* Validation routine */
 140       opc_show_config,                /* Display routine    */
 141       NULL,                           /* Value descriptor   */
 142       NULL,                           /* Help               */
 143     },
 144     {
 145       MTAB_XTD | MTAB_VUN | \
 146       MTAB_VALR | MTAB_NC,            /* Mask               */
 147       0,                              /* Match              */
 148       "NAME",                         /* Print string       */
 149       "NAME",                         /* Match string       */
 150       opc_set_device_name,            /* Validation routine */
 151       opc_show_device_name,           /* Display routine    */
 152       "Set the device name",          /* Value descriptor   */
 153       NULL                            /* Help               */
 154     },
 155 
 156     {
 157       MTAB_unit_valr_nouc,            /* Mask               */
 158       0,                              /* Match              */
 159       "PORT",                         /* Print string       */
 160       "PORT",                         /* Match string       */
 161       opc_set_console_port,           /* validation routine */
 162       opc_show_console_port,          /* Display routine    */
 163       "Set the console port number",  /* Value descriptor   */
 164       NULL                            /* Help               */
 165     },
 166 
 167     {
 168       MTAB_unit_valr_nouc,            /* Mask               */
 169       0,                              /* Match              */
 170       "ADDRESS",                      /* Print string       */
 171       "ADDRESS",                      /* Match string       */
 172       opc_set_console_address,        /* Validation routine */
 173       opc_show_console_address,       /* Display routine    */
 174       "Set the console IP Address",   /* Value descriptor   */
 175       NULL                            /* Help               */
 176     },
 177 
 178     {
 179       MTAB_unit_valr_nouc,            /* Mask               */
 180       0,                              /* Match              */
 181       "PW",                           /* Print string       */
 182       "PW",                           /* Match string       */
 183       opc_set_console_pw,             /* Validation routine */
 184       opc_show_console_pw,            /* Display routine    */
 185       "Set the console password",     /* Value descriptor   */
 186       NULL                            /* Help               */
 187     },
 188 
 189     MTAB_eol
 190 };
 191 
 192 static DEBTAB opc_dt[] =
 193   {
 194     { "NOTIFY", DBG_NOTIFY, NULL },
 195     { "INFO",   DBG_INFO,   NULL },
 196     { "ERR",    DBG_ERR,    NULL },
 197     { "WARN",   DBG_WARN,   NULL },
 198     { "DEBUG",  DBG_DEBUG,  NULL },
 199     { "ALL",    DBG_ALL,    NULL }, // Don't move as it messes up DBG message
 200     { NULL,     0,          NULL }
 201   };
 202 
 203 // Multics only supports a single operator console; but
 204 // it is possible to run multiple Multics instances in a
 205 // cluster. It is also possible to route message output to
 206 // alternate console(s) as I/O devices.
 207 
 208 #define N_OPC_UNITS 1 // default
 209 #define OPC_UNIT_IDX(uptr) ((uptr) - opc_unit)
 210 
 211 // sim_activate counts in instructions, is dependent on the execution
 212 // model
 213 #if defined(LOCKLESS)
 214 // The sim_activate calls are done by the controller thread, which
 215 // has a 1000Hz cycle rate.
 216 // 1K ~= 1 sec
 217 # define ACTIVATE_1SEC 1000
 218 #else
 219 // The sim_activate calls are done by the only thread, with a 4 MHz
 220 // cycle rate.
 221 // 4M ~= 1 sec
 222 # define ACTIVATE_1SEC 4000000
 223 #endif
 224 
 225 static t_stat opc_svc (UNIT * unitp);
 226 
 227 UNIT opc_unit[N_OPC_UNITS_MAX] = {
 228 #if defined(NO_C_ELLIPSIS)
 229   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 230   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 231   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 232   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 233   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 234   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 235   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 236   { UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
 237 #else
 238   [0 ... N_OPC_UNITS_MAX - 1] = {
 239     UDATA (& opc_svc, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
 240   }
 241 #endif
 242 };
 243 
 244 DEVICE opc_dev = {
 245     "OPC",         /* Name                */
 246     opc_unit,      /* Units               */
 247     NULL,          /* Registers           */
 248     opc_mtab,      /* Modifiers           */
 249     N_OPC_UNITS,   /* #units              */
 250     10,            /* Address radix       */
 251     8,             /* Address width       */
 252     1,             /* Address increment   */
 253     8,             /* Address width       */
 254     8,             /* Data width          */
 255     NULL,          /* Examine routine     */
 256     NULL,          /* Deposit routine     */
 257     opc_reset,     /* Reset routine       */
 258     NULL,          /* Boot routine        */
 259     NULL,          /* Attach routine      */
 260     NULL,          /* Detach routine      */
 261     NULL,          /* Context             */
 262     DEV_DEBUG,     /* Flags               */
 263     0,             /* Debug control flags */
 264     opc_dt,        /* Debug flag names    */
 265     NULL,          /* Memory size change  */
 266     NULL,          /* Logical name        */
 267     NULL,          /* Help                */
 268     NULL,          /* Attach help         */
 269     NULL,          /* Help context        */
 270     NULL,          /* Description         */
 271     NULL           /* End                 */
 272 };
 273 
 274 enum console_model { m6001 = 0, m6004 = 1, m6601 = 2 };
 275 
 276 // Hangs off the device structure
 277 typedef struct opc_state_t
 278   {
 279     // Track progress of reads through the autoinput buffer
 280     unsigned char *tailp;
 281     unsigned char *readp;
 282 
 283     // Autoinput buffer pointers
 284     unsigned char *auto_input;
 285     unsigned char *autop;
 286 
 287     // stuff saved from the Read ASCII command
 288     time_t startTime;
 289     UNIT * unitp;
 290 
 291     // telnet connection to console
 292     uv_access console_access;
 293 
 294     enum console_model model;
 295     enum console_mode { opc_no_mode, opc_read_mode, opc_write_mode } io_mode;
 296 
 297     // stuff saved from the Read ASCII command
 298     uint tally;
 299     uint daddr;
 300     int chan;
 301 
 302     // ^T
 303     // Generate "accept" command when dial_ctl announces dialin console
 304     int autoaccept;
 305     // Replace empty console input with "@"
 306     int noempty;
 307     // ATTN flushes typeahead buffer
 308     int attn_flush;
 309 
 310     int simh_buffer_cnt;
 311 
 312     // Track the carrier position to allow tab expansion
 313     // (If the left margin is 1, then the tab stops are 11, 21, 31, 41, ...)
 314     int carrierPosition;
 315 
 316     bool echo;
 317 
 318     bool attn_pressed;
 319     bool simh_attn_pressed;
 320 
 321     bool bcd;
 322 
 323     // Handle escape sequence
 324     bool escapeSequence;
 325 
 326     char device_name [MAX_DEV_NAME_LEN];
 327 
 328 // Multics does console reads with a tally of 64 words; so 256 characters + NUL.
 329 // If the tally is smaller than the contents of the buffer, sendConsole will
 330 // issue a warning and discard the excess.
 331 #define bufsize 257
 332     unsigned char keyboardLineBuffer[bufsize];
 333     bool tabStops [bufsize];
 334 
 335 #define simh_buffer_sz 4096
 336     char simh_buffer[simh_buffer_sz];
 337  } opc_state_t;
 338 
 339 static opc_state_t console_state[N_OPC_UNITS_MAX];
 340 
 341 static char * bcd_code_page =
 342   "01234567"
 343   "89[#@;>?"
 344   " ABCDEFG"
 345   "HI&.](<\\"
 346   "^JKLMNOP"
 347   "QR-$*);'"
 348   "+/STUVWX"
 349   "YZ_,%=\"!";
 350 
 351 //
 352 // Typeahead buffer
 353 //
 354 
 355 #if !defined(TA_BUFFER_SIZE)
 356 # define TA_BUFFER_SIZE 65536
 357 #endif
 358 
 359 static int ta_buffer[TA_BUFFER_SIZE];
 360 static uint ta_cnt  = 0;
 361 static uint ta_next = 0;
 362 static bool ta_ovf  = false;
 363 
 364 static void ta_flush (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 365   {
 366     ta_cnt = ta_next = 0;
 367     ta_ovf = false;
 368   }
 369 
 370 static void ta_push (int c)
     /* [previous][next][first][last][top][bottom][index][help] */
 371   {
 372     // discard overflow
 373     if (ta_cnt >= TA_BUFFER_SIZE)
 374       {
 375         if (! ta_ovf)
 376           sim_print ("typeahead buffer overflow");
 377         ta_ovf = true;
 378         return;
 379       }
 380     ta_buffer [ta_cnt ++] = c;
 381   }
 382 
 383 static int ta_peek (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 384   {
 385     if (ta_next >= ta_cnt)
 386       return SCPE_OK;
 387     int c = ta_buffer[ta_next];
 388     return c;
 389   }
 390 
 391 static int ta_get (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 392   {
 393     if (ta_next >= ta_cnt)
 394       return SCPE_OK;
 395     int c = ta_buffer[ta_next ++];
 396     if (ta_next >= ta_cnt)
 397       ta_flush ();
 398     return c;
 399   }
 400 
 401 static t_stat opc_reset (UNUSED DEVICE * dptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 402   {
 403     for (uint i = 0; i < N_OPC_UNITS_MAX; i ++)
 404       {
 405         console_state[i].io_mode         = opc_no_mode;
 406         console_state[i].tailp           = console_state[i].keyboardLineBuffer;
 407         console_state[i].readp           = console_state[i].keyboardLineBuffer;
 408         console_state[i].carrierPosition = 1;
 409         (void)memset (console_state[i].tabStops, 0, sizeof (console_state[i].tabStops));
 410         console_state[i].escapeSequence  = false;
 411       }
 412     return SCPE_OK;
 413   }
 414 
 415 int check_attn_key (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 416   {
 417     for (uint i = 0; i < opc_dev.numunits; i ++)
 418       {
 419         opc_state_t * csp = console_state + i;
 420         if (csp->attn_pressed)
 421           {
 422              csp->attn_pressed = false;
 423              return (int) i;
 424           }
 425       }
 426     return -1;
 427   }
 428 
 429 // Once-only initialization
 430 
 431 void console_init (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 432   {
 433     opc_reset (& opc_dev);
 434     for (uint i = 0; i < N_OPC_UNITS_MAX; i ++)
 435       {
 436         opc_state_t * csp      = console_state + i;
 437         csp->model             = m6001;
 438         csp->auto_input        = NULL;
 439         csp->autop             = NULL;
 440         csp->attn_pressed      = false;
 441         csp->simh_attn_pressed = false;
 442         csp->simh_buffer_cnt   = 0;
 443         strcpy (csp->console_access.pw, "MulticsRulez");
 444 
 445         csp->autoaccept      = 0;
 446         csp->noempty         = 0;
 447         csp->attn_flush      = 1;
 448         csp->carrierPosition = 1;
 449         csp->escapeSequence  = 1;
 450         (void)memset (csp->tabStops, 0, sizeof (csp->tabStops));
 451       }
 452   }
 453 
 454 // Once-only shutdown
 455 
 456 void console_exit (void) {
     /* [previous][next][first][last][top][bottom][index][help] */
 457   for (uint i = 0; i < N_OPC_UNITS_MAX; i ++) {
 458     opc_state_t * csp = console_state + i;
 459     if (csp->auto_input) {
 460       FREE (csp->auto_input);
 461       csp->auto_input = NULL;
 462     }
 463     if (csp->console_access.telnetp) {
 464       sim_warn ("console_exit freeing console %u telnetp %p\r\n", i, csp->console_access.telnetp);
 465       telnet_free (csp->console_access.telnetp);
 466       csp->console_access.telnetp = NULL;
 467     }
 468   }
 469 }
 470 
 471 static int opc_autoinput_set (UNIT * uptr, UNUSED int32 val,
     /* [previous][next][first][last][top][bottom][index][help] */
 472                                 const char *  cptr, UNUSED void * desc)
 473   {
 474     int devUnitIdx = (int) OPC_UNIT_IDX (uptr);
 475     opc_state_t * csp = console_state + devUnitIdx;
 476 
 477     if (cptr)
 478       {
 479         unsigned char * new = (unsigned char *) strdupesc (cptr);
 480         if (csp-> auto_input)
 481           {
 482             size_t nl = strlen ((char *) new);
 483             size_t ol = strlen ((char *) csp->auto_input);
 484 
 485             unsigned char * old = realloc (csp->auto_input, nl + ol + 1);
 486             if (!old)
 487               {
 488                 (void)fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 489                               __func__, __FILE__, __LINE__);
 490 #if defined(USE_BACKTRACE)
 491 # if defined(SIGUSR2)
 492                 (void)raise(SIGUSR2);
 493                 /*NOTREACHED*/ /* unreachable */
 494 # endif /* if defined(SIGUSR2) */
 495 #endif /* if defined(USE_BACKTRACE) */
 496                 abort();
 497               }
 498             strcpy ((char *) old + ol, (char *) new);
 499             csp->auto_input = old;
 500             FREE (new);
 501           }
 502         else
 503           csp->auto_input = new;
 504       }
 505     else
 506       {
 507         if (csp->auto_input)
 508           FREE (csp->auto_input);
 509         csp->auto_input = NULL;
 510       }
 511     csp->autop = csp->auto_input;
 512     return SCPE_OK;
 513   }
 514 
 515 int clear_opc_autoinput (int32 flag, UNUSED const char * cptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 516   {
 517     opc_state_t * csp = console_state + flag;
 518     if (csp->auto_input)
 519       FREE (csp->auto_input);
 520     csp->auto_input = NULL;
 521     csp->autop = csp->auto_input;
 522     return SCPE_OK;
 523   }
 524 
 525 int add_opc_autoinput (int32 flag, const char * cptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 526   {
 527     opc_state_t * csp = console_state + flag;
 528     unsigned char * new = (unsigned char *) strdupesc (cptr);
 529     if (csp->auto_input)
 530       {
 531         size_t nl = strlen ((char *) new);
 532         size_t ol = strlen ((char *) csp->auto_input);
 533 
 534         unsigned char * old = realloc (csp->auto_input, nl + ol + 1);
 535         if (!old)
 536           {
 537             (void)fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 538                           __func__, __FILE__, __LINE__);
 539 #if defined(USE_BACKTRACE)
 540 # if defined(SIGUSR2)
 541             (void)raise(SIGUSR2);
 542             /*NOTREACHED*/ /* unreachable */
 543 # endif /* if defined(SIGUSR2) */
 544 #endif /* if defined(USE_BACKTRACE) */
 545             abort();
 546           }
 547         strcpy ((char *) old + ol, (char *) new);
 548         csp->auto_input = old;
 549         FREE (new);
 550       }
 551     else
 552       csp->auto_input = new;
 553     csp->autop = csp->auto_input;
 554     return SCPE_OK;
 555   }
 556 
 557 static int opc_autoinput_show (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
 558                                  UNUSED int val, UNUSED const void * desc)
 559   {
 560     int conUnitIdx = (int) OPC_UNIT_IDX (uptr);
 561     opc_state_t * csp = console_state + conUnitIdx;
 562     if (csp->auto_input)
 563       sim_print ("autoinput: '%s'", csp->auto_input);
 564     else
 565       sim_print ("autoinput: empty");
 566     return SCPE_OK;
 567   }
 568 
 569 static t_stat console_attn (UNUSED UNIT * uptr);
 570 
 571 static UNIT attn_unit[N_OPC_UNITS_MAX] = {
 572 #if defined(NO_C_ELLIPSIS)
 573   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 574   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 575   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 576   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 577   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 578   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 579   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL },
 580   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
 581 #else
 582   [0 ... N_OPC_UNITS_MAX - 1] = {
 583     UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
 584   }
 585 #endif
 586 };
 587 
 588 static t_stat console_attn (UNUSED UNIT * uptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 589   {
 590     uint con_unit_idx  = (uint) (uptr - attn_unit);
 591     uint ctlr_port_num = 0; // Consoles are single ported
 592     uint iom_unit_idx  = cables->opc_to_iom[con_unit_idx][ctlr_port_num].iom_unit_idx;
 593     uint chan_num      = cables->opc_to_iom[con_unit_idx][ctlr_port_num].chan_num;
 594     uint dev_code      = 0; // Only a single console on the controller
 595 
 596     send_special_interrupt (iom_unit_idx, chan_num, dev_code, 0, 0);
 597     return SCPE_OK;
 598   }
 599 
 600 void console_attn_idx (int conUnitIdx)
     /* [previous][next][first][last][top][bottom][index][help] */
 601   {
 602     console_attn (attn_unit + conUnitIdx);
 603   }
 604 
 605 #if !defined(__MINGW64__) && !defined(__MINGW32__)
 606 static struct termios ttyTermios;
 607 static bool ttyTermiosOk = false;
 608 
 609 static void newlineOff (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 610   {
 611     if (! isatty (0))
 612       return;
 613     if (! ttyTermiosOk)
 614       {
 615         int rc = tcgetattr (0, & ttyTermios); /* get old flags */
 616         if (rc)
 617            return;
 618         ttyTermiosOk = true;
 619       }
 620     struct termios runtty;
 621     runtty = ttyTermios;
 622     runtty.c_oflag &= (unsigned int) ~OPOST; /* no output edit */
 623 # if defined(__ANDROID__)
 624 #  define TCSA_TYPE TCSANOW
 625     (void)fflush(stdout);
 626     (void)fflush(stderr);
 627 # else
 628 #  define TCSA_TYPE TCSAFLUSH
 629 # endif
 630     tcsetattr (0, TCSA_TYPE, & runtty);
 631   }
 632 
 633 static void newlineOn (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 634   {
 635     if (! isatty (0))
 636       return;
 637     if (! ttyTermiosOk)
 638       return;
 639 # if defined(__ANDROID__)
 640     (void)fflush(stdout);
 641     (void)fflush(stderr);
 642 # endif
 643     tcsetattr (0, TCSA_TYPE, & ttyTermios);
 644   }
 645 #endif /* if !defined(__MINGW64__) && !defined(__MINGW32__) */
 646 
 647 static void handleRCP (uint con_unit_idx, char * text)
     /* [previous][next][first][last][top][bottom][index][help] */
 648   {
 649 // It appears that Cygwin doesn't grok "%ms"
 650 
 651 
 652 
 653 
 654 
 655 
 656 
 657 
 658     size_t len = strlen (text);
 659     char   label [len + 1];
 660     char   with  [len + 1];
 661     char   drive [len + 1];
 662     int rc = sscanf (text, "%*d.%*d RCP: Mount Reel %s %s ring on %s",
 663                 label, with, drive);
 664     if (rc == 3)
 665       {
 666         bool withring = (strcmp (with, "with") == 0);
 667         char labelDotTap[strlen (label) + strlen (".tap") + 1];
 668         strcpy (labelDotTap, label);
 669         strcat (labelDotTap, ".tap");
 670         attachTape (labelDotTap, withring, drive);
 671         return;
 672       }
 673 
 674     rc = sscanf (text, "%*d.%*d RCP: Remount Reel %s %s ring on %s",
 675                 label, with, drive);
 676     if (rc == 3)
 677       {
 678         bool withring = (strcmp (with, "with") == 0);
 679         char labelDotTap [strlen (label) + strlen (".tap") + 1];
 680         strcpy (labelDotTap, label);
 681         strcat (labelDotTap, ".tap");
 682         attachTape (labelDotTap, withring, drive);
 683         return;
 684       }
 685 
 686 //  1629  as   dial_ctl_: Channel d.h000 dialed to Initializer
 687 
 688     if (console_state[con_unit_idx].autoaccept)
 689       {
 690         rc = sscanf (text, "%*d  as   dial_ctl_: Channel %s dialed to Initializer",
 691                      label);
 692         if (rc == 1)
 693           {
 694             //sim_printf (" dial system <%s>\r\n", label);
 695             opc_autoinput_set (opc_unit + con_unit_idx, 0, "accept ", NULL);
 696             opc_autoinput_set (opc_unit + con_unit_idx, 0, label, NULL);
 697             opc_autoinput_set (opc_unit + con_unit_idx, 0, "\r", NULL);
 698 // XXX This is subject to race conditions
 699             if (console_state[con_unit_idx].io_mode != opc_read_mode)
 700               console_state[con_unit_idx].attn_pressed = true;
 701             return;
 702           }
 703       }
 704   }
 705 
 706 // Send entered text to the IOM.
 707 static void sendConsole (int conUnitIdx, word12 stati)
     /* [previous][next][first][last][top][bottom][index][help] */
 708   {
 709     opc_state_t * csp   = console_state + conUnitIdx;
 710     uint tally          = csp->tally;
 711     uint ctlr_port_num  = 0; // Consoles are single ported
 712     uint iomUnitIdx     = cables->opc_to_iom[conUnitIdx][ctlr_port_num].iom_unit_idx;
 713     uint chan_num       = cables->opc_to_iom[conUnitIdx][ctlr_port_num].chan_num;
 714     iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan_num];
 715 
 716     //ASSURE (csp->io_mode == opc_read_mode);
 717     if (csp->io_mode != opc_read_mode)
 718       {
 719         sim_warn ("%s called with io_mode != opc_read_mode (%d)\n",
 720                   __func__, csp->io_mode);
 721         return;
 722       }
 723 
 724     uint n_chars = (uint) (csp->tailp - csp->readp);
 725     uint n_words;
 726     if (csp->bcd)
 727         // + 2 for the !1 newline
 728         n_words = ((n_chars+2) + 5) / 6;
 729       else
 730         n_words = (n_chars + 3) / 4;
 731     // The "+1" is for them empty line case below
 732     word36 buf[n_words + 1];
 733     // Make Oracle lint happy
 734     (void)memset (buf, 0, sizeof (word36) * (n_words + 1));
 735     word36 * bufp = buf;
 736 
 737     // Multics doesn't seem to like empty lines; it the line buffer
 738     // is empty and there is room in the I/O buffer, send a line kill.
 739     if ((!csp->bcd) && csp->noempty && n_chars == 0 && tally)
 740       {
 741         n_chars = 1;
 742         n_words = 1;
 743         putbits36_9 (bufp, 0, '@');
 744         tally --;
 745       }
 746     else
 747       {
 748         int bcd_nl_state = 0;
 749         while (tally && csp->readp < csp->tailp)
 750           {
 751             if (csp->bcd)
 752               {
 753                 //* bufp = 0171717171717ul;
 754                 //* bufp = 0202020202020ul;
 755                 * bufp = 0;
 756                 for (uint charno = 0; charno < 4; ++ charno)
 757                   {
 758                     unsigned char c;
 759                     if (csp->readp >= csp->tailp)
 760                       {
 761                         if (bcd_nl_state == 0)
 762                           {
 763                             c = '!';
 764                             bcd_nl_state = 1;
 765                           }
 766                         else if (bcd_nl_state == 1)
 767                           {
 768                             c = '1';
 769                             bcd_nl_state = 2;
 770                           }
 771                         else
 772                           break;
 773                       }
 774                     else
 775                       c = (unsigned char) (* csp->readp ++);
 776                     c = (unsigned char) toupper (c);
 777                     int i;
 778                     for (i = 0; i < 64; i ++)
 779                       if (bcd_code_page[i] == c)
 780                         break;
 781                     if (i >= 64)
 782                       {
 783                         sim_warn ("Character %o does not map to BCD; replacing with '?'\n", c);
 784                         i = 017; //-V536
 785                       }
 786                     putbits36_6 (bufp, charno * 6, (word6) i);
 787                   }
 788               }
 789             else
 790               {
 791                 * bufp = 0ul;
 792                 for (uint charno = 0; charno < 4; ++ charno)
 793                   {
 794                     if (csp->readp >= csp->tailp)
 795                       break;
 796                     unsigned char c = (unsigned char) (* csp->readp ++);
 797                     c &= 0177;  // Multics get consternated about this
 798                     putbits36_9 (bufp, charno * 9, c);
 799                   }
 800               }
 801             bufp ++;
 802             tally --;
 803           }
 804         if (csp->readp < csp->tailp)
 805           {
 806             sim_warn ("opc_iom_io: discarding %d characters from end of line\n",
 807                       (int) (csp->tailp - csp->readp));
 808           }
 809       }
 810 
 811     iom_indirect_data_service (iomUnitIdx, chan_num, buf, & n_words, true);
 812 
 813     p->charPos = n_chars % 4;
 814     p->stati   = (word12) stati;
 815 
 816     csp->readp   = csp->keyboardLineBuffer;
 817     csp->tailp   = csp->keyboardLineBuffer;
 818     csp->io_mode = opc_no_mode;
 819 
 820     send_terminate_interrupt (iomUnitIdx, chan_num);
 821   }
 822 
 823 static void console_putchar (int conUnitIdx, char ch);
 824 static void console_putstr  (int conUnitIdx, char * str);
 825 
 826 // Process characters entered on keyboard or autoinput
 827 static void consoleProcessIdx (int conUnitIdx)
     /* [previous][next][first][last][top][bottom][index][help] */
 828   {
 829     opc_state_t * csp = console_state + conUnitIdx;
 830     int c;
 831 
 832 //// Move data from keyboard buffers into type-ahead buffer
 833 
 834     for (;;)
 835       {
 836         c = sim_poll_kbd ();
 837         if (c == SCPE_OK)
 838           c = accessGetChar (& csp->console_access);
 839 
 840         // Check for stop signaled by scp
 841 
 842         if (breakEnable && stop_cpu)
 843           {
 844             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 845             return;
 846           }
 847 
 848         // Check for ^E
 849         //   (Windows doesn't handle ^E as a signal; need to explicitly test
 850         //   for it.)
 851 
 852         if (breakEnable && c == SCPE_STOP)
 853           {
 854             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 855             stop_cpu = 1;
 856             return; // User typed ^E to stop simulation
 857           }
 858 
 859         // Check for scp break
 860 
 861         if (breakEnable && c == SCPE_BREAK)
 862           {
 863             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 864             stop_cpu = 1;
 865             return; // User typed ^E to stop simulation
 866           }
 867 
 868         // End of available input
 869 
 870         if (c == SCPE_OK)
 871           break;
 872 
 873         // sanity test
 874 
 875         if (c < SCPE_KFLAG)
 876           {
 877             sim_printf ("impossible %d %o\n", c, c);
 878             continue; // Should be impossible
 879           }
 880 
 881         // translate to ascii
 882 
 883         int ch = c - SCPE_KFLAG;
 884 
 885         // XXX This is subject to race conditions
 886         // If the console is not in read mode and ESC or ^A
 887         // is pressed, signal Multics for ATTN.
 888         if (csp->io_mode != opc_read_mode)
 889           {
 890             if (ch == '\033' || ch == '\001') // escape or ^A
 891               {
 892                 if (csp->attn_flush)
 893                   ta_flush ();
 894                 csp->attn_pressed = true;
 895                 continue;
 896               }
 897           }
 898 
 899 
 900 
 901 
 902 
 903 
 904 
 905 
 906 
 907 
 908 
 909 
 910 
 911 
 912 
 913 
 914 
 915 
 916 
 917 
 918 
 919 
 920         // ^S
 921 
 922         if (ch == 023) // ^S scp command
 923           {
 924             if (! csp->simh_attn_pressed)
 925               {
 926                 ta_flush ();
 927                 csp->simh_attn_pressed = true;
 928                 csp->simh_buffer_cnt = 0;
 929                 console_putstr (conUnitIdx, "^S\r\n" SIM_NAME "> ");
 930               }
 931             continue;
 932           }
 933 
 934 //// ^P  Prompt
 935 
 936         // ATTN is ambiguous; in read mode, it cancels the read.
 937         // In write mode, is discards output and asks for a prompt.
 938 
 939         // ^P will check the mode, and if it is not input, will do an
 940         // ATTN signal. There is still a small race window; if Multics
 941         // is in the process of starting read mode, it may end up canceling
 942         // the read.
 943 
 944         if (ch == 020) { // ^P
 945           if (csp->io_mode != opc_read_mode) {
 946             if (csp->attn_flush)
 947               ta_flush ();
 948             csp->attn_pressed = true;
 949           }
 950           continue;
 951         }
 952 
 953 //// ^T
 954 
 955         if (ch == 024) // ^T
 956           {
 957             char buf[256];
 958             char cms[3] = "?RW";
 959             // XXX Assumes console 0
 960             (void)sprintf (buf, "^T attn %c %c cnt %d next %d\r\n",
 961                            console_state[0].attn_pressed+'0',
 962                            cms[console_state[0].io_mode],
 963                            ta_cnt, ta_next);
 964             console_putstr (conUnitIdx, buf);
 965 #if defined(THREADZ) || defined(LOCKLESS)
 966             (void) sprintf (buf, "syncClockMode %c syncClockModeMasterIdx %u\r\n",
 967                             syncClockMode ? 'T' : 'F',
 968                             syncClockModeMasterIdx);
 969             console_putstr (conUnitIdx, buf);
 970             for (int i = 0; i < N_CPU_UNITS_MAX; i ++) {
 971               (void) sprintf (buf, "CPU %c %s %05o:%06o\r\n"
 972                                    "      executing %c syncClockModeMaster %c workAllocation %4ld\r\n"
 973                                    "      inMultics %c syncClockModeCache %c isSlave %c\r\n",
 974                               "ABCDEFGH"[i],
 975                               cycle_str (cpus[i].cycle),
 976                               cpus[i].PPR.PSR,
 977                               cpus[i].PPR.IC,
 978                               cpus[i].executing ? 'T' : 'F',
 979                               cpus[i].syncClockModeMaster ? 'T' : 'F',
 980                               cpus[i].workAllocation,
 981                               cpus[i].inMultics ? 'T' : 'F',
 982                               cpus[i].syncClockModeCache ? 'T' : 'F',
 983                               cpus[i].isSlave ? 'T' : 'F');
 984               console_putstr (conUnitIdx, buf);
 985             }
 986 #endif /* if defined(THREADZ) || defined(LOCKLESS) */
 987 
 988             continue;
 989           }
 990 
 991 //// In ^S mode (accumulating a scp command)?
 992 
 993         if (csp->simh_attn_pressed)
 994           {
 995             ta_get ();
 996             if (ch == '\177' || ch == '\010')  // backspace/del
 997               {
 998                 if (csp->simh_buffer_cnt > 0)
 999                   {
1000                     -- csp->simh_buffer_cnt;
1001                     csp->simh_buffer[csp->simh_buffer_cnt] = 0;
1002                     console_putstr (conUnitIdx,  "\b \b");
1003                   }
1004                 return;
1005               }
1006 
1007             //// scp ^R
1008 
1009             if (ch == '\022')  // ^R
1010               {
1011                 console_putstr (conUnitIdx, "^R\r\n" SIM_NAME "> ");
1012                 for (int i = 0; i < csp->simh_buffer_cnt; i ++)
1013                   console_putchar (conUnitIdx, (char) (csp->simh_buffer[i]));
1014                 return;
1015               }
1016 
1017             //// scp ^U
1018 
1019             if (ch == '\025')  // ^U
1020               {
1021                 console_putstr (conUnitIdx, "^U\r\n" SIM_NAME "> ");
1022                 csp->simh_buffer_cnt = 0;
1023                 return;
1024               }
1025 
1026             //// scp CR/LF
1027 
1028             if (ch == '\012' || ch == '\015')  // CR/LF
1029               {
1030                 console_putstr (conUnitIdx,  "\r\n");
1031                 csp->simh_buffer[csp->simh_buffer_cnt] = 0;
1032 
1033                 char * cptr = csp->simh_buffer;
1034                 char gbuf[simh_buffer_sz];
1035                 cptr = (char *) get_glyph (cptr, gbuf, 0); /* get command glyph */
1036                 if (strlen (gbuf))
1037                   {
1038                     CTAB *cmdp;
1039                     if ((cmdp = find_cmd (gbuf))) /* lookup command */
1040                       {
1041                         t_stat stat = cmdp->action (cmdp->arg, cptr);
1042                            /* if found, exec */
1043                         if (stat != SCPE_OK)
1044                           {
1045                             char buf[4096];
1046                             (void)sprintf (buf, "\r%s returned %d '%s'\r\n",
1047                                            SIM_NAME, stat, sim_error_text (stat));
1048                             console_putstr (conUnitIdx,  buf);
1049                           }
1050                       }
1051                     else
1052                        console_putstr (conUnitIdx,
1053                          "\rUnrecognized " SIM_NAME " command.\r\n");
1054                   }
1055                 csp->simh_buffer_cnt   = 0;
1056                 csp->simh_buffer[0]    = 0;
1057                 csp->simh_attn_pressed = false;
1058                 return;
1059               }
1060 
1061             //// scp ESC/^D/^Z
1062 
1063             if (ch == '\033' || ch == '\004' || ch == '\032')  // ESC/^D/^Z
1064               {
1065                 console_putstr (conUnitIdx,  "\r\n" SIM_NAME " cancel\r\n");
1066                 // Empty input buffer
1067                 csp->simh_buffer_cnt   = 0;
1068                 csp->simh_buffer[0]    = 0;
1069                 csp->simh_attn_pressed = false;
1070                 return;
1071               }
1072 
1073             //// scp isprint?
1074 
1075             if (isprint (ch))
1076               {
1077                 // silently drop buffer overrun
1078                 if (csp->simh_buffer_cnt + 1 >= simh_buffer_sz)
1079                   return;
1080                 csp->simh_buffer[csp->simh_buffer_cnt ++] = (char) ch;
1081                 console_putchar (conUnitIdx, (char) ch);
1082                 return;
1083               }
1084             return;
1085           } // if (simh_attn_pressed)
1086 
1087         // Save the character
1088 
1089         ta_push (c);
1090       }
1091 
1092 //// Check for stop signaled by scp
1093 
1094     if (breakEnable && stop_cpu)
1095       {
1096         console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
1097         return;
1098       }
1099 
1100 //// Console is reading and autoinput is ready
1101 ////   Move line of text from autoinput buffer to console buffer
1102 
1103     if (csp->io_mode == opc_read_mode &&
1104         csp->autop != NULL)
1105       {
1106         int announce = 1;
1107         for (;;)
1108           {
1109             if (csp->tailp >= csp->keyboardLineBuffer + sizeof (csp->keyboardLineBuffer))
1110              {
1111                 sim_warn ("getConsoleInput: Buffer full; flushing autoinput.\n");
1112                 sendConsole (conUnitIdx, 04000); // Normal status
1113                 return;
1114               }
1115             unsigned char c = * (csp->autop);
1116             if (c == 4) // eot
1117               {
1118                 FREE (csp->auto_input);
1119                 csp->auto_input = NULL;
1120                 csp->autop      = NULL;
1121                 // Empty input buffer
1122                 csp->readp      = csp->keyboardLineBuffer;
1123                 csp->tailp      = csp->keyboardLineBuffer;
1124                 sendConsole (conUnitIdx, 04310); // Null line, status operator
1125                                                  // distracted
1126                 console_putstr (conUnitIdx,  "CONSOLE: RELEASED\r\n");
1127                 return;
1128               }
1129             if (c == 030 || c == 031) // ^X ^Y
1130               {
1131                 // an expect string is in the autoinput buffer; wait for it
1132                 // to be processed
1133                 return;
1134               }
1135             if (c == 0)
1136               {
1137                 FREE (csp->auto_input);
1138                 csp->auto_input = NULL;
1139                 csp->autop      = NULL;
1140                 goto eol;
1141               }
1142             if (announce)
1143               {
1144                 console_putstr (conUnitIdx,  "[auto-input] ");
1145                 announce = 0;
1146               }
1147             csp->autop ++;
1148 
1149             if (c == '\012' || c == '\015')
1150               {
1151 eol:
1152                 if (csp->echo)
1153                   console_putstr (conUnitIdx,  "\r\n");
1154                 sendConsole (conUnitIdx, 04000); // Normal status
1155                 return;
1156               }
1157             else
1158               {
1159                 * csp->tailp ++ = c;
1160                 if (csp->echo)
1161                   console_putchar (conUnitIdx, (char) c);
1162               }
1163           } // for (;;)
1164       } // if (autop)
1165 
1166 //// Read mode and nothing in console buffer
1167 ////   Check for timeout
1168 
1169     if (csp->io_mode == opc_read_mode &&
1170         csp->tailp == csp->keyboardLineBuffer)
1171       {
1172         if (csp->startTime + 30 <= time (NULL))
1173           {
1174             console_putstr (conUnitIdx,  "CONSOLE: TIMEOUT\r\n");
1175             csp->readp = csp->keyboardLineBuffer;
1176             csp->tailp = csp->keyboardLineBuffer;
1177             sendConsole (conUnitIdx, 04310); // Null line, status operator
1178                                              // distracted
1179           }
1180       }
1181 
1182 //// Peek at the character in the typeahead buffer
1183 
1184     c = ta_peek ();
1185 
1186     // No data
1187     if (c == SCPE_OK)
1188         return;
1189 
1190     // Convert from scp encoding to ASCII
1191     if (c < SCPE_KFLAG)
1192       {
1193         sim_printf ("impossible %d %o\n", c, c);
1194         return; // Should be impossible
1195       }
1196 
1197     // translate to ascii
1198 
1199     int ch = c - SCPE_KFLAG;
1200 
1201 // XXX This is subject to race conditions
1202     if (csp->io_mode != opc_read_mode)
1203       {
1204         if (ch == '\033' || ch == '\001') // escape or ^A
1205           {
1206             ta_get ();
1207             csp->attn_pressed = true;
1208           }
1209         return;
1210       }
1211 
1212     if (ch == '\177' || ch == '\010')  // backspace/del
1213       {
1214         ta_get ();
1215         if (csp->tailp > csp->keyboardLineBuffer)
1216           {
1217             * csp->tailp = 0;
1218             -- csp->tailp;
1219             if (csp->echo)
1220               console_putstr (conUnitIdx,  "\b \b");
1221           }
1222         return;
1223       }
1224 
1225     if (ch == '\022')  // ^R
1226       {
1227         ta_get ();
1228         if (csp->echo)
1229           {
1230             console_putstr (conUnitIdx,  "^R\r\n");
1231             for (unsigned char * p = csp->keyboardLineBuffer; p < csp->tailp; p ++)
1232               console_putchar (conUnitIdx, (char) (*p));
1233             return;
1234           }
1235       }
1236 
1237     if (ch == '\025')  // ^U
1238       {
1239         ta_get ();
1240         console_putstr (conUnitIdx,  "^U\r\n");
1241         csp->tailp = csp->keyboardLineBuffer;
1242         return;
1243       }
1244 
1245     if (ch == '\030')  // ^X
1246       {
1247         ta_get ();
1248         console_putstr (conUnitIdx,  "^X\r\n");
1249         csp->tailp = csp->keyboardLineBuffer;
1250         return;
1251       }
1252 
1253     if (ch == '\012' || ch == '\015')  // CR/LF
1254       {
1255         ta_get ();
1256         if (csp->echo)
1257           console_putstr (conUnitIdx,  "\r\n");
1258         sendConsole (conUnitIdx, 04000); // Normal status
1259         return;
1260       }
1261 
1262     if (ch == '\033' || ch == '\004' || ch == '\032')  // ESC/^D/^Z
1263       {
1264         ta_get ();
1265         console_putstr (conUnitIdx,  "\r\n");
1266         // Empty input buffer
1267         csp->readp = csp->keyboardLineBuffer;
1268         csp->tailp = csp->keyboardLineBuffer;
1269         sendConsole (conUnitIdx, 04310); // Null line, status operator
1270                                          // distracted
1271         console_putstr (conUnitIdx,  "CONSOLE: RELEASED\n");
1272         return;
1273       }
1274 
1275     if (isprint (ch))
1276       {
1277         // silently drop buffer overrun
1278         ta_get ();
1279         if (csp->tailp >= csp->keyboardLineBuffer + sizeof (csp->keyboardLineBuffer))
1280           return;
1281 
1282         * csp->tailp ++ = (unsigned char) ch;
1283         if (csp->echo)
1284           console_putchar (conUnitIdx, (char) ch);
1285         return;
1286       }
1287     // ignore other chars...
1288     ta_get ();
1289     return;
1290   }
1291 
1292 void consoleProcess (void)
     /* [previous][next][first][last][top][bottom][index][help] */
1293   {
1294     for (int conUnitIdx = 0; conUnitIdx < (int) opc_dev.numunits; conUnitIdx ++)
1295       consoleProcessIdx (conUnitIdx);
1296   }
1297 
1298 /*
1299  * opc_iom_cmd ()
1300  *
1301  * Handle a device command.  Invoked by the IOM while processing a PCW
1302  * or IDCW.
1303  */
1304 
1305 iom_cmd_rc_t opc_iom_cmd (uint iomUnitIdx, uint chan) {
     /* [previous][next][first][last][top][bottom][index][help] */
1306   iom_cmd_rc_t rc = IOM_CMD_PROCEED;
1307 #if defined(TESTING)
1308   cpu_state_t * cpup = _cpup;
1309 #endif
1310 
1311 #if defined(LOCKLESS)
1312   lock_libuv ();
1313 #endif
1314 
1315   iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan];
1316   uint con_unit_idx   = get_ctlr_idx (iomUnitIdx, chan);
1317   UNIT * unitp        = & opc_unit[con_unit_idx];
1318   opc_state_t * csp   = console_state + con_unit_idx;
1319 
1320   p->dev_code = p->IDCW_DEV_CODE;
1321   p->stati = 0;
1322   //int conUnitIdx = (int) d->devUnitIdx;
1323 
1324   // The 6001 only executes the PCW DCW command; the 6601 executes
1325   // the PCW DCW and (at least) the first DCW list item.
1326   // When Multics uses the 6601, the PCW DCW is always 040 RESET.
1327   // The 040 RESET will trigger the DCW list read.
1328   // will change this.
1329 
1330   // IDCW?
1331   if (IS_IDCW (p)) {
1332     // IDCW
1333 
1334     switch (p->IDCW_DEV_CMD) {
1335       case 000: // CMD 00 Request status
1336         sim_debug (DBG_DEBUG, & opc_dev, "%s: Status request\n", __func__);
1337         csp->io_mode = opc_no_mode;
1338         p->stati     = 04000;
1339         break;
1340 
1341       case 003:               // Read BCD
1342         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read BCD echoed\n", __func__);
1343         csp->io_mode = opc_read_mode;
1344         p->recordResidue --;
1345         csp->echo    = true;
1346         csp->bcd     = true;
1347         p->stati     = 04000;
1348         break;
1349 
1350       case 013:               // Write BCD
1351         sim_debug (DBG_DEBUG, & opc_dev, "%s: Write BCD\n", __func__);
1352         p->isRead    = false;
1353         csp->bcd     = true;
1354         csp->io_mode = opc_write_mode;
1355         p->recordResidue --;
1356         p->stati     = 04000;
1357         break;
1358 
1359       case 023:               // Read ASCII
1360         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read ASCII echoed\n", __func__);
1361         csp->io_mode = opc_read_mode;
1362         p->recordResidue --;
1363         csp->echo    = true;
1364         csp->bcd     = false;
1365         p->stati     = 04000;
1366         break;
1367 
1368       case 033:               // Write ASCII
1369         sim_debug (DBG_DEBUG, & opc_dev, "%s: Write ASCII\n", __func__);
1370         p->isRead    = false;
1371         csp->bcd     = false;
1372         csp->io_mode = opc_write_mode;
1373         p->recordResidue --;
1374         p->stati     = 04000;
1375         break;
1376 
1377 // Model 6001 vs. 6601.
1378 //
1379 // When Multics switches to 6601 mode, the PCW DCW is always 040 RESET; the
1380 // bootload and 6001 code never does that. Therefore, we use the 040
1381 // as an indication that the DCW list should be processed.
1382 // All of the other device commands will return IOM_CMD_DISCONNECT, stopping
1383 // parsing of the channel command program. This one will return IOM_CMD_PROCEED,
1384 // causing the parser to move to the DCW list.
1385 
1386       case 040:               // Reset
1387         sim_debug (DBG_DEBUG, & opc_dev, "%s: Reset\n", __func__);
1388         p->stati = 04000;
1389         // T&D probing
1390         //if (p->IDCW_DEV_CODE == 077) {
1391           // T&D uses dev code 77 to test for the console device;
1392           // it ignores dev code, and so returns OK here.
1393           //p->stati = 04502; // invalid device code
1394           // if (p->IDCW_CHAN_CTRL == 0) { sim_warn ("%s: TERMINATE_BUG\n", __func__); return IOM_CMD_DISCONNECT; }
1395         //}
1396         break;
1397 
1398       case 043:               // Read ASCII unechoed
1399         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read ASCII unechoed\n", __func__);
1400         csp->io_mode = opc_read_mode;
1401         p->recordResidue --;
1402         csp->echo    = false;
1403         csp->bcd     = false;
1404         p->stati     = 04000;
1405         break;
1406 
1407       case 051:               // Write Alert -- Ring Bell
1408         sim_debug (DBG_DEBUG, & opc_dev, "%s: Alert\n", __func__);
1409         p->isRead = false;
1410         console_putstr ((int) con_unit_idx,  "CONSOLE: ALERT\r\n");
1411         console_putchar ((int) con_unit_idx, '\a');
1412         p->stati  = 04000;
1413         if (csp->model == m6001 && p->isPCW) {
1414           rc = IOM_CMD_DISCONNECT;
1415           goto done;
1416         }
1417         break;
1418 
1419       case 057:               // Read ID (according to AN70-1)
1420         // FIXME: No support for Read ID; appropriate values are not known
1421         //[CAC] Looking at the bootload console code, it seems more
1422         // concerned about the device responding, rather than the actual
1423         // returned value. Make some thing up.
1424         sim_debug (DBG_DEBUG, & opc_dev, "%s: Read ID\n", __func__);
1425         p->stati = 04500;
1426         if (csp->model == m6001 && p->isPCW) {
1427           rc = IOM_CMD_DISCONNECT;
1428           goto done;
1429         }
1430         break;
1431 
1432       case 060:               // LOCK MCA
1433         sim_debug (DBG_DEBUG, & opc_dev, "%s: Lock\n", __func__);
1434         console_putstr ((int) con_unit_idx,  "CONSOLE: LOCK\r\n");
1435         p->stati = 04000;
1436         break;
1437 
1438       case 063:               // UNLOCK MCA
1439         sim_debug (DBG_DEBUG, & opc_dev, "%s: Unlock\n", __func__);
1440         console_putstr ((int) con_unit_idx,  "CONSOLE: UNLOCK\r\n");
1441         p->stati = 04000;
1442         break;
1443 
1444       default:
1445         sim_debug (DBG_DEBUG, & opc_dev, "%s: Unknown command 0%o\n", __func__, p->IDCW_DEV_CMD);
1446         p->stati = 04501; // command reject, invalid instruction code
1447         rc = IOM_CMD_ERROR;
1448         goto done;
1449     } // switch IDCW_DEV_CMD
1450     goto done;
1451   } // IDCW
1452 
1453   // Not IDCW; TDCW are captured in IOM, so must be IOTD or IOTP
1454   switch (csp->io_mode) {
1455     case opc_no_mode:
1456       sim_warn ("%s: Unexpected IOTx\n", __func__);
1457       rc = IOM_CMD_ERROR;
1458       goto done;
1459 
1460     case opc_read_mode: {
1461         if (csp->tailp != csp->keyboardLineBuffer) {
1462           sim_warn ("%s: Discarding previously buffered input.\n", __func__);
1463         }
1464         uint tally = p->DDCW_TALLY;
1465         uint daddr = p->DDCW_ADDR;
1466 
1467         if (tally == 0) {
1468           tally = 4096;
1469         }
1470 
1471         csp->tailp     = csp->keyboardLineBuffer;
1472         csp->readp     = csp->keyboardLineBuffer;
1473         csp->startTime = time (NULL);
1474         csp->tally     = tally;
1475         csp->daddr     = daddr;
1476         csp->unitp     = unitp;
1477         csp->chan      = (int) chan;
1478 
1479         // If Multics has gone seriously awry (eg crash
1480         // to BCE during boot), the autoinput will become
1481         // wedged waiting for the expect string 'Ready'.
1482         // We just went to read mode; if we are waiting
1483         // on an expect string, it is never coming because
1484         // console access is blocked by the expect code.
1485         // Throw out the script if this happens....
1486 
1487         // If there is autoinput and it is at ^X or ^Y
1488         if (csp->autop && (*csp->autop == 030 || *csp->autop == 031)) { // ^X ^Y
1489           // We are wedged.
1490           // Clear the autoinput buffer; this will cancel the
1491           // expect wait and any remaining script, returning
1492           // control of the console to the user.
1493           // Assuming opc0.
1494           clear_opc_autoinput (ASSUME0, NULL);
1495           ta_flush ();
1496           sim_printf \
1497               ("\r\nScript wedged and abandoned; autoinput and typeahead buffers flushed\r\n");
1498         }
1499         rc = IOM_CMD_PENDING; // command in progress; do not send terminate interrupt
1500         goto done;
1501       } // case opc_read_mode
1502 
1503     case opc_write_mode: {
1504         uint tally = p->DDCW_TALLY;
1505 
1506 // We would hope that number of valid characters in the last word
1507 // would be in DCW_18_20_CP, but it seems to reliably be zero.
1508 
1509         if (tally == 0) {
1510           tally = 4096;
1511         }
1512 
1513         word36 buf[tally];
1514         iom_indirect_data_service (iomUnitIdx, chan, buf, & tally, false);
1515         p->initiate = false;
1516 
1517 
1518 
1519 
1520 
1521 
1522 
1523 
1524 
1525 
1526 
1527 
1528 
1529 
1530 
1531 
1532 
1533 
1534 
1535 
1536 
1537 
1538 
1539 
1540 
1541 
1542 
1543 
1544 
1545 
1546 
1547         // Tally is in words, not chars.
1548         char text[tally * 4 + 1];
1549         char * textp = text;
1550         word36 * bufp = buf;
1551         * textp = 0;
1552 #if !defined(__MINGW64__)
1553         newlineOff ();
1554 #endif
1555         // 0 == no escape character seen
1556         // 1 ==  ! seen
1557         // 2 == !! seen
1558         int escape_cnt = 0;
1559 
1560         while (tally) {
1561           word36 datum = * bufp ++;
1562           tally --;
1563           if (csp->bcd) {
1564             // Lifted from tolts_util_.pl1:bci_to_ascii
1565             for (int i = 0; i < 6; i ++) {
1566               word36 narrow_char = datum >> 30; // slide leftmost char
1567                                                 //  into low byte
1568               datum = datum << 6; // lose the leftmost char
1569               narrow_char &= 077;
1570               char ch = bcd_code_page [narrow_char];
1571               if (escape_cnt == 2) {
1572                 console_putchar ((int) con_unit_idx, ch);
1573                 * textp ++ = ch;
1574                 escape_cnt = 0;
1575               } else if (ch == '!') {
1576                 escape_cnt ++;
1577               } else if (escape_cnt == 1) {
1578                 uint lp = (uint)narrow_char;
1579                 // !0 is mapped to !1
1580                 // !1 to !9, ![, !#, !@, !;, !>, !?    1 to 15 newlines
1581                 if (lp == 060 /* + */ || lp == 075 /* = */) { // POLTS
1582                   p->stati = 04320;
1583                   goto done;
1584                 }
1585                 if (lp == 0)
1586                   lp = 1;
1587                 if (lp >= 16) {
1588                   console_putstr ((int) con_unit_idx, "\f");
1589                   //* textp ++ = '\f';
1590                 } else {
1591                   for (uint i = 0; i < lp; i ++) {
1592                     console_putstr ((int) con_unit_idx, "\r\n");
1593                     //* textp ++ = '\r';
1594                     //* textp ++ = '\n';
1595                   }
1596                 }
1597                 escape_cnt = 0;
1598               } else if (ch == '?') {
1599                 escape_cnt = 0;
1600               } else {
1601                 console_putchar ((int) con_unit_idx, ch);
1602                 * textp ++ = ch;
1603                 escape_cnt = 0;
1604               }
1605             }
1606           } else {
1607             for (int i = 0; i < 4; i ++) {
1608               word36 wide_char = datum >> 27; // slide leftmost char
1609                                               //  into low byte
1610               datum = datum << 9; // lose the leftmost char
1611               char ch = wide_char & 0x7f;
1612               if (ch != 0177 && ch != 0) {
1613                 console_putchar ((int) con_unit_idx, ch);
1614                 * textp ++ = ch;
1615               }
1616             }
1617           }
1618         }
1619         * textp ++ = 0;
1620 
1621         // autoinput expect
1622         if (csp->autop && * csp->autop == 030) {
1623           //   ^xstring\0
1624           //size_t expl = strlen ((char *) (csp->autop + 1));
1625           //   ^xstring^x
1626           size_t expl = strcspn ((char *) (csp->autop + 1), "\030");
1627 
1628           if (strncmp (text, (char *) (csp->autop + 1), expl) == 0) {
1629             csp->autop += expl + 2;
1630             sim_activate (& attn_unit[con_unit_idx], ACTIVATE_1SEC);
1631           }
1632         }
1633         // autoinput expect
1634         if (csp->autop && * csp->autop == 031) {
1635           //   ^ystring\0
1636           //size_t expl = strlen ((char *) (csp->autop + 1));
1637           //   ^ystring^y
1638           size_t expl = strcspn ((char *) (csp->autop + 1), "\031");
1639 
1640           char needle [expl + 1];
1641           strncpy (needle, (char *) csp->autop + 1, expl);
1642           needle [expl] = 0;
1643           if (strstr (text, needle)) {
1644             csp->autop += expl + 2;
1645             sim_activate (& attn_unit[con_unit_idx], ACTIVATE_1SEC);
1646           }
1647         }
1648         handleRCP (con_unit_idx, text);
1649 #if !defined(__MINGW64__)
1650         newlineOn ();
1651 #endif
1652         p->stati = 04000;
1653         goto done;
1654       } // case opc_write_mode
1655   } // switch io_mode
1656 
1657 done:
1658 #if defined(LOCKLESS)
1659   unlock_libuv ();
1660 #endif
1661   return rc;
1662 } // opc_iom_cmd
1663 
1664 static t_stat opc_svc (UNIT * unitp)
     /* [previous][next][first][last][top][bottom][index][help] */
1665   {
1666     int con_unit_idx   = (int) OPC_UNIT_IDX (unitp);
1667     uint ctlr_port_num = 0; // Consoles are single ported
1668     uint iom_unit_idx  = cables->opc_to_iom[con_unit_idx][ctlr_port_num].iom_unit_idx;
1669     uint chan_num      = cables->opc_to_iom[con_unit_idx][ctlr_port_num].chan_num;
1670 
1671     opc_iom_cmd (iom_unit_idx, chan_num);
1672     return SCPE_OK;
1673   }
1674 
1675 static t_stat opc_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1676                                  UNUSED int val, UNUSED const void * desc)
1677   {
1678     sim_print ("%d units\n", opc_dev.numunits);
1679     return SCPE_OK;
1680   }
1681 
1682 static t_stat opc_set_nunits (UNUSED UNIT * uptr, int32 UNUSED value,
     /* [previous][next][first][last][top][bottom][index][help] */
1683                                 const char * cptr, UNUSED void * desc)
1684   {
1685     if (! cptr)
1686       return SCPE_ARG;
1687     int n = atoi (cptr);
1688     if (n < 1 || n > N_OPC_UNITS_MAX)
1689       return SCPE_ARG;
1690     opc_dev.numunits = (uint32) n;
1691     return SCPE_OK;
1692   }
1693 
1694 static config_value_list_t cfg_on_off[] =
1695   {
1696     { "off",     0 },
1697     { "on",      1 },
1698     { "disable", 0 },
1699     { "enable",  1 },
1700     { NULL,      0 }
1701   };
1702 
1703 static config_value_list_t cfg_model[] =
1704   {
1705     { "m6001", m6001 },
1706     { "m6004", m6004 },
1707     { "m6601", m6601 },
1708     { NULL,    0 }
1709   };
1710 
1711 static config_list_t opc_config_list[] =
1712   {
1713    { "autoaccept", 0, 1, cfg_on_off },
1714    { "noempty",    0, 1, cfg_on_off },
1715    { "attn_flush", 0, 1, cfg_on_off },
1716    { "model",      1, 0, cfg_model },
1717    { NULL,         0, 0, NULL }
1718   };
1719 
1720 static t_stat opc_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1721                               const char * cptr, UNUSED void * desc)
1722   {
1723     int devUnitIdx           = (int) OPC_UNIT_IDX (uptr);
1724     opc_state_t * csp        = console_state + devUnitIdx;
1725 // XXX Minor bug; this code doesn't check for trailing garbage
1726     config_state_t cfg_state = { NULL, NULL };
1727 
1728     for (;;)
1729       {
1730         int64_t v;
1731         int rc = cfg_parse (__func__, cptr, opc_config_list,
1732                            & cfg_state, & v);
1733         if (rc == -1) // done
1734           break;
1735 
1736         if (rc == -2) // error
1737           {
1738             cfg_parse_done (& cfg_state);
1739             return SCPE_ARG;
1740           }
1741         const char * p = opc_config_list[rc].name;
1742 
1743         if (strcmp (p, "autoaccept") == 0)
1744           {
1745             csp->autoaccept = (int) v;
1746             continue;
1747           }
1748 
1749         if (strcmp (p, "noempty") == 0)
1750           {
1751             csp->noempty = (int) v;
1752             continue;
1753           }
1754 
1755         if (strcmp (p, "attn_flush") == 0)
1756           {
1757             csp->attn_flush = (int) v;
1758             continue;
1759           }
1760 
1761         if (strcmp (p, "model") == 0)
1762           {
1763             csp->model = (enum console_model) v;
1764             continue;
1765           }
1766 
1767         sim_warn ("error: opc_set_config: Invalid cfg_parse rc <%d>\n",
1768                   rc);
1769         cfg_parse_done (& cfg_state);
1770         return SCPE_ARG;
1771       } // process statements
1772     cfg_parse_done (& cfg_state);
1773     return SCPE_OK;
1774   }
1775 
1776 static t_stat opc_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1777                                UNUSED int  val, UNUSED const void * desc)
1778   {
1779     int devUnitIdx    = (int) OPC_UNIT_IDX (uptr);
1780     opc_state_t * csp = console_state + devUnitIdx;
1781     sim_msg ("flags    : ");
1782     sim_msg ("autoaccept=%d, ", csp->autoaccept);
1783     sim_msg ("noempty=%d, ",    csp->noempty);
1784     sim_msg ("attn_flush=%d",   csp->attn_flush);
1785     return SCPE_OK;
1786   }
1787 
1788 static t_stat opc_show_device_name (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1789                                     UNUSED int val, UNUSED const void * desc)
1790   {
1791     int n = (int) OPC_UNIT_IDX (uptr);
1792     if (n < 0 || n >= N_OPC_UNITS_MAX)
1793       return SCPE_ARG;
1794     sim_printf("name     : OPC%d", n);
1795     return SCPE_OK;
1796   }
1797 
1798 static t_stat opc_set_device_name (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1799                                    const char * cptr, UNUSED void * desc)
1800   {
1801     int n = (int) OPC_UNIT_IDX (uptr);
1802     if (n < 0 || n >= N_OPC_UNITS_MAX)
1803       return SCPE_ARG;
1804     if (cptr)
1805       {
1806         strncpy (console_state[n].device_name, cptr, MAX_DEV_NAME_LEN-1);
1807         console_state[n].device_name[MAX_DEV_NAME_LEN-1] = 0;
1808       }
1809     else
1810       console_state[n].device_name[0] = 0;
1811     return SCPE_OK;
1812   }
1813 
1814 static t_stat opc_set_console_port (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1815                                     const char * cptr, UNUSED void * desc)
1816   {
1817     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1818     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1819       return SCPE_ARG;
1820 
1821     if (cptr)
1822       {
1823         int port = atoi (cptr);
1824         if (port < 0 || port > 65535) // 0 is 'disable'
1825           return SCPE_ARG;
1826         console_state[dev_idx].console_access.port = port;
1827         if (port != 0)
1828           sim_msg ("[OPC emulation: TELNET server port set to %d]\r\n", (int)port);
1829       }
1830     else
1831       console_state[dev_idx].console_access.port = 0;
1832     return SCPE_OK;
1833   }
1834 
1835 static t_stat opc_show_console_port (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1836                                        UNUSED int val, UNUSED const void * desc)
1837   {
1838     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1839     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1840       return SCPE_ARG;
1841     if (console_state[dev_idx].console_access.port)
1842       sim_printf("port     : %d", console_state[dev_idx].console_access.port);
1843     else
1844       sim_printf("port     : disabled");
1845     return SCPE_OK;
1846   }
1847 
1848 static t_stat opc_set_console_address (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1849                                     const char * cptr, UNUSED void * desc)
1850   {
1851     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1852     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1853       return SCPE_ARG;
1854 
1855     if (console_state[dev_idx].console_access.address)
1856       {
1857         FREE (console_state[dev_idx].console_access.address);
1858         console_state[dev_idx].console_access.address = NULL;
1859       }
1860 
1861     if (cptr)
1862       {
1863         console_state[dev_idx].console_access.address = strdup (cptr);
1864         if (!console_state[dev_idx].console_access.address)
1865           {
1866             (void)fprintf (stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
1867                            __func__, __FILE__, __LINE__);
1868 #if defined(USE_BACKTRACE)
1869 # if defined(SIGUSR2)
1870             (void)raise(SIGUSR2);
1871             /*NOTREACHED*/ /* unreachable */
1872 # endif /* if defined(SIGUSR2) */
1873 #endif /* if defined(USE_BACKTRACE) */
1874             abort();
1875           }
1876         sim_msg ("\r[OPC emulation: OPC%d server address set to %s]\r\n",
1877                 dev_idx, console_state[dev_idx].console_access.address);
1878       }
1879 
1880     return SCPE_OK;
1881   }
1882 
1883 static t_stat opc_show_console_address (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1884                                        UNUSED int val, UNUSED const void * desc)
1885   {
1886     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1887     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1888       return SCPE_ARG;
1889     if (console_state[dev_idx].console_access.address)
1890       sim_printf("address  : %s", console_state[dev_idx].console_access.address);
1891     else
1892       sim_printf("address  : any");
1893     return SCPE_OK;
1894   }
1895 
1896 static t_stat opc_set_console_pw (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1897                                     const char * cptr, UNUSED void * desc)
1898   {
1899     long dev_idx = (int) OPC_UNIT_IDX (uptr);
1900     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1901       return SCPE_ARG;
1902 
1903     if (cptr && (strlen(cptr) > 0))
1904       {
1905         char token[strlen (cptr)+1];
1906         int rc = sscanf (cptr, "%s", token);
1907         if (rc != 1)
1908           return SCPE_ARG;
1909         if (strlen (token) > PW_SIZE)
1910           return SCPE_ARG;
1911         strcpy (console_state[dev_idx].console_access.pw, token);
1912       }
1913     else
1914       {
1915         sim_msg ("no password\n");
1916         console_state[dev_idx].console_access.pw[0] = 0;
1917       }
1918 
1919     return SCPE_OK;
1920   }
1921 
1922 static t_stat opc_show_console_pw (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1923                                        UNUSED int val, UNUSED const void * desc)
1924   {
1925     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1926     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1927       return SCPE_ARG;
1928     sim_printf("password : %s", console_state[dev_idx].console_access.pw);
1929     return SCPE_OK;
1930   }
1931 
1932 static void console_putstr (int conUnitIdx, char * str)
     /* [previous][next][first][last][top][bottom][index][help] */
1933   {
1934     size_t l = strlen (str);
1935     for (size_t i = 0; i < l; i ++)
1936       sim_putchar (str[i]);
1937     opc_state_t * csp = console_state + conUnitIdx;
1938     if (csp->console_access.loggedOn)
1939       accessStartWrite (csp->console_access.client, str,
1940                            (ssize_t) l);
1941   }
1942 
1943 static void consolePutchar0 (int conUnitIdx, char ch) {
     /* [previous][next][first][last][top][bottom][index][help] */
1944   opc_state_t * csp = console_state + conUnitIdx;
1945   sim_putchar (ch);
1946   if (csp->console_access.loggedOn)
1947     accessStartWrite (csp->console_access.client, & ch, 1);
1948 }
1949 
1950 static void console_putchar (int conUnitIdx, char ch) {
     /* [previous][next][first][last][top][bottom][index][help] */
1951   opc_state_t * csp = console_state + conUnitIdx;
1952   if (csp->escapeSequence) { // Prior character was an escape
1953     csp->escapeSequence = false;
1954     if (ch == '1') { // Set tab
1955       if (csp->carrierPosition >= 1 && csp->carrierPosition <= 256) {
1956         csp->tabStops[csp->carrierPosition] = true;
1957       }
1958     } else if (ch == '2') { // Clear all tabs
1959       (void)memset (csp->tabStops, 0, sizeof (csp->tabStops));
1960     } else { // Unrecognized
1961       sim_warn ("Unrecognized escape sequence \\033\\%03o\r\n", ch);
1962     }
1963   } else if (isprint (ch)) {
1964     consolePutchar0 (conUnitIdx, ch);
1965     csp->carrierPosition ++;
1966   } else if (ch == '\t') {  // Tab
1967     while (csp->carrierPosition < bufsize - 1) {
1968       consolePutchar0 (conUnitIdx, ' ');
1969       csp->carrierPosition ++;
1970       if (csp->tabStops[csp->carrierPosition])
1971         break;
1972     }
1973   } else if (ch == '\b') { // Backspace
1974       consolePutchar0 (conUnitIdx, ch);
1975       csp->carrierPosition --;
1976   } else if (ch == '\f' || ch == '\r') { // Formfeed, Carriage return
1977       consolePutchar0 (conUnitIdx, ch);
1978       csp->carrierPosition = 1;
1979   } else if (ch == '\033') { //-V536  // Escape
1980       csp->escapeSequence = true;
1981   } else { // Non-printing and we don't recognize a carriage motion character, so just print it...
1982       consolePutchar0 (conUnitIdx, ch);
1983   }
1984 }
1985 
1986 static void consoleConnectPrompt (uv_tcp_t * client)
     /* [previous][next][first][last][top][bottom][index][help] */
1987   {
1988     accessStartWriteStr (client, "password: \r\n");
1989     uv_access * console_access = (uv_access *) client->data;
1990     console_access->pwPos      = 0;
1991   }
1992 
1993 void startRemoteConsole (void)
     /* [previous][next][first][last][top][bottom][index][help] */
1994   {
1995     for (int conUnitIdx = 0; conUnitIdx < N_OPC_UNITS_MAX; conUnitIdx ++)
1996       {
1997         console_state[conUnitIdx].console_access.connectPrompt = consoleConnectPrompt;
1998         console_state[conUnitIdx].console_access.connected     = NULL;
1999         console_state[conUnitIdx].console_access.useTelnet     = true;
2000 #if defined(CONSOLE_FIX)
2001 # if defined(LOCKLESS)
2002         lock_libuv ();
2003 # endif
2004 #endif
2005         uv_open_access (& console_state[conUnitIdx].console_access);
2006 #if defined(CONSOLE_FIX)
2007 # if defined(LOCKLESS)
2008         unlock_libuv ();
2009 # endif
2010 #endif
2011       }
2012   }

/* [previous][next][first][last][top][bottom][index][help] */