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

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