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-2023 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              sim_usleep (1000);
 427              return (int) i;
 428           }
 429       }
 430     return -1;
 431   }
 432 
 433 // Once-only initialization
 434 
 435 void console_init (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 436   {
 437     opc_reset (& opc_dev);
 438     for (uint i = 0; i < N_OPC_UNITS_MAX; i ++)
 439       {
 440         opc_state_t * csp      = console_state + i;
 441         csp->model             = m6001;
 442         csp->auto_input        = NULL;
 443         csp->autop             = NULL;
 444         csp->attn_pressed      = false;
 445         csp->simh_attn_pressed = false;
 446         csp->simh_buffer_cnt   = 0;
 447         strcpy (csp->console_access.pw, "MulticsRulez");
 448 
 449         csp->autoaccept      = 0;
 450         csp->noempty         = 0;
 451         csp->attn_flush      = 1;
 452         csp->carrierPosition = 1;
 453         csp->escapeSequence  = 1;
 454         memset (csp->tabStops, 0, sizeof (csp->tabStops));
 455       }
 456   }
 457 
 458 // Once-only shutdown
 459 
 460 void console_exit (void) {
     /* [previous][next][first][last][top][bottom][index][help] */
 461   for (uint i = 0; i < N_OPC_UNITS_MAX; i ++) {
 462     opc_state_t * csp = console_state + i;
 463     if (csp->auto_input) {
 464       FREE (csp->auto_input);
 465       csp->auto_input = NULL;
 466     }
 467     if (csp->console_access.telnetp) {
 468       sim_warn ("console_exit freeing console %u telnetp %p\r\n", i, csp->console_access.telnetp);
 469       telnet_free (csp->console_access.telnetp);
 470       csp->console_access.telnetp = NULL;
 471     }
 472   }
 473 }
 474 
 475 static int opc_autoinput_set (UNIT * uptr, UNUSED int32 val,
     /* [previous][next][first][last][top][bottom][index][help] */
 476                                 const char *  cptr, UNUSED void * desc)
 477   {
 478     int devUnitIdx = (int) OPC_UNIT_IDX (uptr);
 479     opc_state_t * csp = console_state + devUnitIdx;
 480 
 481     if (cptr)
 482       {
 483         unsigned char * new = (unsigned char *) strdupesc (cptr);
 484         if (csp-> auto_input)
 485           {
 486             size_t nl = strlen ((char *) new);
 487             size_t ol = strlen ((char *) csp->auto_input);
 488 
 489             unsigned char * old = realloc (csp->auto_input, nl + ol + 1);
 490             if (!old)
 491               {
 492                 fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 493                         __func__, __FILE__, __LINE__);
 494 #if defined(USE_BACKTRACE)
 495 # ifdef SIGUSR2
 496                 (void)raise(SIGUSR2);
 497                 /*NOTREACHED*/ /* unreachable */
 498 # endif /* ifdef SIGUSR2 */
 499 #endif /* if defined(USE_BACKTRACE) */
 500                 abort();
 501               }
 502             strcpy ((char *) old + ol, (char *) new);
 503             csp->auto_input = old;
 504             FREE (new);
 505           }
 506         else
 507           csp->auto_input = new;
 508       }
 509     else
 510       {
 511         if (csp->auto_input)
 512           FREE (csp->auto_input);
 513         csp->auto_input = NULL;
 514       }
 515     csp->autop = csp->auto_input;
 516     return SCPE_OK;
 517   }
 518 
 519 int clear_opc_autoinput (int32 flag, UNUSED const char * cptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 520   {
 521     opc_state_t * csp = console_state + flag;
 522     if (csp->auto_input)
 523       FREE (csp->auto_input);
 524     csp->auto_input = NULL;
 525     csp->autop = csp->auto_input;
 526     return SCPE_OK;
 527   }
 528 
 529 int add_opc_autoinput (int32 flag, const char * cptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 530   {
 531     opc_state_t * csp = console_state + flag;
 532     unsigned char * new = (unsigned char *) strdupesc (cptr);
 533     if (csp->auto_input)
 534       {
 535         size_t nl = strlen ((char *) new);
 536         size_t ol = strlen ((char *) csp->auto_input);
 537 
 538         unsigned char * old = realloc (csp->auto_input, nl + ol + 1);
 539         if (!old)
 540           {
 541             fprintf(stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
 542                     __func__, __FILE__, __LINE__);
 543 #if defined(USE_BACKTRACE)
 544 # ifdef SIGUSR2
 545             (void)raise(SIGUSR2);
 546             /*NOTREACHED*/ /* unreachable */
 547 # endif /* ifdef SIGUSR2 */
 548 #endif /* if defined(USE_BACKTRACE) */
 549             abort();
 550           }
 551         strcpy ((char *) old + ol, (char *) new);
 552         csp->auto_input = old;
 553         FREE (new);
 554       }
 555     else
 556       csp->auto_input = new;
 557     csp->autop = csp->auto_input;
 558     return SCPE_OK;
 559   }
 560 
 561 static int opc_autoinput_show (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
 562                                  UNUSED int val, UNUSED const void * desc)
 563   {
 564     int conUnitIdx = (int) OPC_UNIT_IDX (uptr);
 565     opc_state_t * csp = console_state + conUnitIdx;
 566     if (csp->auto_input)
 567       sim_print ("autoinput: '%s'", csp->auto_input);
 568     else
 569       sim_print ("autoinput: empty");
 570     return SCPE_OK;
 571   }
 572 
 573 static t_stat console_attn (UNUSED UNIT * uptr);
 574 
 575 static UNIT attn_unit[N_OPC_UNITS_MAX] = {
 576 #ifdef NO_C_ELLIPSIS
 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   { UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL }
 585 #else
 586   [0 ... N_OPC_UNITS_MAX - 1] = {
 587     UDATA (& console_attn, 0, 0), 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL
 588   }
 589 #endif
 590 };
 591 
 592 static t_stat console_attn (UNUSED UNIT * uptr)
     /* [previous][next][first][last][top][bottom][index][help] */
 593   {
 594     uint con_unit_idx  = (uint) (uptr - attn_unit);
 595     uint ctlr_port_num = 0; // Consoles are single ported
 596     uint iom_unit_idx  = cables->opc_to_iom[con_unit_idx][ctlr_port_num].iom_unit_idx;
 597     uint chan_num      = cables->opc_to_iom[con_unit_idx][ctlr_port_num].chan_num;
 598     uint dev_code      = 0; // Only a single console on the controller
 599 
 600     send_special_interrupt (iom_unit_idx, chan_num, dev_code, 0, 0);
 601     return SCPE_OK;
 602   }
 603 
 604 void console_attn_idx (int conUnitIdx)
     /* [previous][next][first][last][top][bottom][index][help] */
 605   {
 606     console_attn (attn_unit + conUnitIdx);
 607   }
 608 
 609 #ifndef __MINGW64__
 610 # ifndef __MINGW32__
 611 static struct termios ttyTermios;
 612 static bool ttyTermiosOk = false;
 613 
 614 static void newlineOff (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 615   {
 616     if (! isatty (0))
 617       return;
 618     if (! ttyTermiosOk)
 619       {
 620         int rc = tcgetattr (0, & ttyTermios); /* get old flags */
 621         if (rc)
 622            return;
 623         ttyTermiosOk = true;
 624       }
 625     struct termios runtty;
 626     runtty = ttyTermios;
 627     runtty.c_oflag &= (unsigned int) ~OPOST; /* no output edit */
 628     tcsetattr (0, TCSAFLUSH, & runtty);
 629   }
 630 
 631 static void newlineOn (void)
     /* [previous][next][first][last][top][bottom][index][help] */
 632   {
 633     if (! isatty (0))
 634       return;
 635     if (! ttyTermiosOk)
 636       return;
 637     tcsetattr (0, TCSAFLUSH, & ttyTermios);
 638   }
 639 # endif /* ifndef __MINGW32__ */
 640 #endif /* ifndef __MINGW64__ */
 641 
 642 static void handleRCP (uint con_unit_idx, char * text)
     /* [previous][next][first][last][top][bottom][index][help] */
 643   {
 644 // It appears that Cygwin doesn't grok "%ms"
 645 
 646 
 647 
 648 
 649 
 650 
 651 
 652 
 653     size_t len = strlen (text);
 654     char   label [len + 1];
 655     char   with  [len + 1];
 656     char   drive [len + 1];
 657     int rc = sscanf (text, "%*d.%*d RCP: Mount Reel %s %s ring on %s",
 658                 label, with, drive);
 659     if (rc == 3)
 660       {
 661         bool withring = (strcmp (with, "with") == 0);
 662         char labelDotTap[strlen (label) + strlen (".tap") + 1];
 663         strcpy (labelDotTap, label);
 664         strcat (labelDotTap, ".tap");
 665         attachTape (labelDotTap, withring, drive);
 666         return;
 667       }
 668 
 669     rc = sscanf (text, "%*d.%*d RCP: Remount Reel %s %s ring on %s",
 670                 label, with, drive);
 671     if (rc == 3)
 672       {
 673         bool withring = (strcmp (with, "with") == 0);
 674         char labelDotTap [strlen (label) + strlen (".tap") + 1];
 675         strcpy (labelDotTap, label);
 676         strcat (labelDotTap, ".tap");
 677         attachTape (labelDotTap, withring, drive);
 678         return;
 679       }
 680 
 681 //  1629  as   dial_ctl_: Channel d.h000 dialed to Initializer
 682 
 683     if (console_state[con_unit_idx].autoaccept)
 684       {
 685         rc = sscanf (text, "%*d  as   dial_ctl_: Channel %s dialed to Initializer",
 686                      label);
 687         if (rc == 1)
 688           {
 689             //sim_printf (" dial system <%s>\r\n", label);
 690             opc_autoinput_set (opc_unit + con_unit_idx, 0, "accept ", NULL);
 691             opc_autoinput_set (opc_unit + con_unit_idx, 0, label, NULL);
 692             opc_autoinput_set (opc_unit + con_unit_idx, 0, "\r", NULL);
 693 // XXX This is subject to race conditions
 694             if (console_state[con_unit_idx].io_mode != opc_read_mode)
 695               console_state[con_unit_idx].attn_pressed = true;
 696             return;
 697           }
 698       }
 699   }
 700 
 701 // Send entered text to the IOM.
 702 static void sendConsole (int conUnitIdx, word12 stati)
     /* [previous][next][first][last][top][bottom][index][help] */
 703   {
 704     opc_state_t * csp   = console_state + conUnitIdx;
 705     uint tally          = csp->tally;
 706     uint ctlr_port_num  = 0; // Consoles are single ported
 707     uint iomUnitIdx     = cables->opc_to_iom[conUnitIdx][ctlr_port_num].iom_unit_idx;
 708     uint chan_num       = cables->opc_to_iom[conUnitIdx][ctlr_port_num].chan_num;
 709     iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan_num];
 710 
 711     //ASSURE (csp->io_mode == opc_read_mode);
 712     if (csp->io_mode != opc_read_mode)
 713       {
 714         sim_warn ("%s called with io_mode != opc_read_mode (%d)\n",
 715                   __func__, csp->io_mode);
 716         return;
 717       }
 718 
 719     uint n_chars = (uint) (csp->tailp - csp->readp);
 720     uint n_words;
 721     if (csp->bcd)
 722         // + 2 for the !1 newline
 723         n_words = ((n_chars+2) + 5) / 6;
 724       else
 725         n_words = (n_chars + 3) / 4;
 726     // The "+1" is for them empty line case below
 727     word36 buf[n_words + 1];
 728     // Make Oracle lint happy
 729     memset (buf, 0, sizeof (word36) * (n_words + 1));
 730     word36 * bufp = buf;
 731 
 732     // Multics doesn't seem to like empty lines; it the line buffer
 733     // is empty and there is room in the I/O buffer, send a line kill.
 734     if ((!csp->bcd) && csp->noempty && n_chars == 0 && tally)
 735       {
 736         n_chars = 1;
 737         n_words = 1;
 738         putbits36_9 (bufp, 0, '@');
 739         tally --;
 740       }
 741     else
 742       {
 743         int bcd_nl_state = 0;
 744         while (tally && csp->readp < csp->tailp)
 745           {
 746             if (csp->bcd)
 747               {
 748                 //* bufp = 0171717171717ul;
 749                 //* bufp = 0202020202020ul;
 750                 * bufp = 0;
 751                 for (uint charno = 0; charno < 4; ++ charno)
 752                   {
 753                     unsigned char c;
 754                     if (csp->readp >= csp->tailp)
 755                       {
 756                         if (bcd_nl_state == 0)
 757                           {
 758                             c = '!';
 759                             bcd_nl_state = 1;
 760                           }
 761                         else if (bcd_nl_state == 1)
 762                           {
 763                             c = '1';
 764                             bcd_nl_state = 2;
 765                           }
 766                         else
 767                           break;
 768                       }
 769                     else
 770                       c = (unsigned char) (* csp->readp ++);
 771                     c = (unsigned char) toupper (c);
 772                     int i;
 773                     for (i = 0; i < 64; i ++)
 774                       if (bcd_code_page[i] == c)
 775                         break;
 776                     if (i >= 64)
 777                       {
 778                         sim_warn ("Character %o does not map to BCD; replacing with '?'\n", c);
 779                         i = 017; //-V536
 780                       }
 781                     putbits36_6 (bufp, charno * 6, (word6) i);
 782                   }
 783               }
 784             else
 785               {
 786                 * bufp = 0ul;
 787                 for (uint charno = 0; charno < 4; ++ charno)
 788                   {
 789                     if (csp->readp >= csp->tailp)
 790                       break;
 791                     unsigned char c = (unsigned char) (* csp->readp ++);
 792                     c &= 0177;  // Multics get consternated about this
 793                     putbits36_9 (bufp, charno * 9, c);
 794                   }
 795               }
 796             bufp ++;
 797             tally --;
 798           }
 799         if (csp->readp < csp->tailp)
 800           {
 801             sim_warn ("opc_iom_io: discarding %d characters from end of line\n",
 802                       (int) (csp->tailp - csp->readp));
 803           }
 804       }
 805 
 806     iom_indirect_data_service (iomUnitIdx, chan_num, buf, & n_words, true);
 807 
 808     p->charPos = n_chars % 4;
 809     p->stati   = (word12) stati;
 810 
 811     csp->readp   = csp->keyboardLineBuffer;
 812     csp->tailp   = csp->keyboardLineBuffer;
 813     csp->io_mode = opc_no_mode;
 814 
 815     send_terminate_interrupt (iomUnitIdx, chan_num);
 816   }
 817 
 818 static void console_putchar (int conUnitIdx, char ch);
 819 static void console_putstr  (int conUnitIdx, char * str);
 820 
 821 // Process characters entered on keyboard or autoinput
 822 static void consoleProcessIdx (int conUnitIdx)
     /* [previous][next][first][last][top][bottom][index][help] */
 823   {
 824     opc_state_t * csp = console_state + conUnitIdx;
 825     int c;
 826 
 827 //// Move data from keyboard buffers into type-ahead buffer
 828 
 829     for (;;)
 830       {
 831         c = sim_poll_kbd ();
 832         if (c == SCPE_OK)
 833           c = accessGetChar (& csp->console_access);
 834 
 835         // Check for stop signaled by scp
 836 
 837         if (breakEnable && stop_cpu)
 838           {
 839             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 840             return;
 841           }
 842 
 843         // Check for ^E
 844         //   (Windows doesn't handle ^E as a signal; need to explicitly test
 845         //   for it.)
 846 
 847         if (breakEnable && c == SCPE_STOP)
 848           {
 849             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 850             stop_cpu = 1;
 851             return; // User typed ^E to stop simulation
 852           }
 853 
 854         // Check for scp break
 855 
 856         if (breakEnable && c == SCPE_BREAK)
 857           {
 858             console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
 859             stop_cpu = 1;
 860             return; // User typed ^E to stop simulation
 861           }
 862 
 863         // End of available input
 864 
 865         if (c == SCPE_OK)
 866           break;
 867 
 868         // sanity test
 869 
 870         if (c < SCPE_KFLAG)
 871           {
 872             sim_printf ("impossible %d %o\n", c, c);
 873             continue; // Should be impossible
 874           }
 875 
 876         // translate to ascii
 877 
 878         int ch = c - SCPE_KFLAG;
 879 
 880         // XXX This is subject to race conditions
 881         // If the console is not in read mode and ESC or ^A
 882         // is pressed, signal Multics for ATTN.
 883         if (csp->io_mode != opc_read_mode)
 884           {
 885             if (ch == '\033' || ch == '\001') // escape or ^A
 886               {
 887                 if (csp->attn_flush)
 888                   ta_flush ();
 889                 csp->attn_pressed = true;
 890                 continue;
 891               }
 892           }
 893 
 894 
 895 
 896 
 897 
 898 
 899 
 900 
 901 
 902 
 903 
 904 
 905 
 906 
 907 
 908 
 909 
 910 
 911 
 912 
 913 
 914 
 915         // ^S
 916 
 917         if (ch == 023) // ^S scp command
 918           {
 919             if (! csp->simh_attn_pressed)
 920               {
 921                 ta_flush ();
 922                 csp->simh_attn_pressed = true;
 923                 csp->simh_buffer_cnt = 0;
 924                 console_putstr (conUnitIdx, "^S\r\n" SIM_NAME "> ");
 925               }
 926             continue;
 927           }
 928 
 929 //// ^P  Prompt
 930 
 931         // ATTN is ambiguous; in read mode, it cancels the read.
 932         // In write mode, is discards output and asks for a prompt.
 933 
 934         // ^P will check the mode, and if it is not input, will do an
 935         // ATTN signal. There is still a small race window; if Multics
 936         // is in the process of starting read mode, it may end up canceling
 937         // the read.
 938 
 939         if (ch == 020) { // ^P
 940           sim_usleep (1000);
 941           if (csp->io_mode != opc_read_mode) {
 942             if (csp->attn_flush)
 943               ta_flush ();
 944             csp->attn_pressed = true;
 945           }
 946           continue;
 947         }
 948 
 949 //// ^T
 950 
 951         if (ch == 024) // ^T
 952           {
 953             char buf[256];
 954             char cms[3] = "?RW";
 955             // XXX Assumes console 0
 956             sprintf (buf, "^T attn %c %c cnt %d next %d\r\n",
 957                      console_state[0].attn_pressed+'0',
 958                      cms[console_state[0].io_mode],
 959                      ta_cnt, ta_next);
 960             console_putstr (conUnitIdx, buf);
 961             continue;
 962           }
 963 
 964 //// In ^S mode (accumulating a scp command)?
 965 
 966         if (csp->simh_attn_pressed)
 967           {
 968             ta_get ();
 969             if (ch == '\177' || ch == '\010')  // backspace/del
 970               {
 971                 if (csp->simh_buffer_cnt > 0)
 972                   {
 973                     -- csp->simh_buffer_cnt;
 974                     csp->simh_buffer[csp->simh_buffer_cnt] = 0;
 975                     console_putstr (conUnitIdx,  "\b \b");
 976                   }
 977                 return;
 978               }
 979 
 980             //// scp ^R
 981 
 982             if (ch == '\022')  // ^R
 983               {
 984                 console_putstr (conUnitIdx, "^R\r\n" SIM_NAME "> ");
 985                 for (int i = 0; i < csp->simh_buffer_cnt; i ++)
 986                   console_putchar (conUnitIdx, (char) (csp->simh_buffer[i]));
 987                 return;
 988               }
 989 
 990             //// scp ^U
 991 
 992             if (ch == '\025')  // ^U
 993               {
 994                 console_putstr (conUnitIdx, "^U\r\n" SIM_NAME "> ");
 995                 csp->simh_buffer_cnt = 0;
 996                 return;
 997               }
 998 
 999             //// scp CR/LF
1000 
1001             if (ch == '\012' || ch == '\015')  // CR/LF
1002               {
1003                 console_putstr (conUnitIdx,  "\r\n");
1004                 csp->simh_buffer[csp->simh_buffer_cnt] = 0;
1005 
1006                 char * cptr = csp->simh_buffer;
1007                 char gbuf[simh_buffer_sz];
1008                 cptr = (char *) get_glyph (cptr, gbuf, 0); /* get command glyph */
1009                 if (strlen (gbuf))
1010                   {
1011                     CTAB *cmdp;
1012                     if ((cmdp = find_cmd (gbuf))) /* lookup command */
1013                       {
1014                         t_stat stat = cmdp->action (cmdp->arg, cptr);
1015                            /* if found, exec */
1016                         if (stat != SCPE_OK)
1017                           {
1018                             char buf[4096];
1019                             sprintf (buf, "\r%s returned %d '%s'\r\n",
1020                               SIM_NAME, stat, sim_error_text (stat));
1021                             console_putstr (conUnitIdx,  buf);
1022                           }
1023                       }
1024                     else
1025                        console_putstr (conUnitIdx,
1026                          "\rUnrecognized " SIM_NAME " command.\r\n");
1027                   }
1028                 csp->simh_buffer_cnt   = 0;
1029                 csp->simh_buffer[0]    = 0;
1030                 csp->simh_attn_pressed = false;
1031                 return;
1032               }
1033 
1034             //// scp ESC/^D/^Z
1035 
1036             if (ch == '\033' || ch == '\004' || ch == '\032')  // ESC/^D/^Z
1037               {
1038                 console_putstr (conUnitIdx,  "\r\n" SIM_NAME " cancel\r\n");
1039                 // Empty input buffer
1040                 csp->simh_buffer_cnt   = 0;
1041                 csp->simh_buffer[0]    = 0;
1042                 csp->simh_attn_pressed = false;
1043                 return;
1044               }
1045 
1046             //// scp isprint?
1047 
1048             if (isprint (ch))
1049               {
1050                 // silently drop buffer overrun
1051                 if (csp->simh_buffer_cnt + 1 >= simh_buffer_sz)
1052                   return;
1053                 csp->simh_buffer[csp->simh_buffer_cnt ++] = (char) ch;
1054                 console_putchar (conUnitIdx, (char) ch);
1055                 return;
1056               }
1057             return;
1058           } // if (simh_attn_pressed)
1059 
1060         // Save the character
1061 
1062         ta_push (c);
1063       }
1064 
1065 //// Check for stop signaled by scp
1066 
1067     if (breakEnable && stop_cpu)
1068       {
1069         console_putstr (conUnitIdx,  " Got <sim stop> \r\n");
1070         return;
1071       }
1072 
1073 //// Console is reading and autoinput is ready
1074 ////   Move line of text from autoinput buffer to console buffer
1075 
1076     if (csp->io_mode == opc_read_mode &&
1077         csp->autop != NULL)
1078       {
1079         int announce = 1;
1080         for (;;)
1081           {
1082             if (csp->tailp >= csp->keyboardLineBuffer + sizeof (csp->keyboardLineBuffer))
1083              {
1084                 sim_warn ("getConsoleInput: Buffer full; flushing autoinput.\n");
1085                 sendConsole (conUnitIdx, 04000); // Normal status
1086                 return;
1087               }
1088             unsigned char c = * (csp->autop);
1089             if (c == 4) // eot
1090               {
1091                 FREE (csp->auto_input);
1092                 csp->auto_input = NULL;
1093                 csp->autop      = NULL;
1094                 // Empty input buffer
1095                 csp->readp      = csp->keyboardLineBuffer;
1096                 csp->tailp      = csp->keyboardLineBuffer;
1097                 sendConsole (conUnitIdx, 04310); // Null line, status operator
1098                                                  // distracted
1099                 console_putstr (conUnitIdx,  "CONSOLE: RELEASED\r\n");
1100                 sim_usleep (1000);
1101                 return;
1102               }
1103             if (c == 030 || c == 031) // ^X ^Y
1104               {
1105                 // an expect string is in the autoinput buffer; wait for it
1106                 // to be processed
1107                 return;
1108               }
1109             if (c == 0)
1110               {
1111                 FREE (csp->auto_input);
1112                 csp->auto_input = NULL;
1113                 csp->autop      = NULL;
1114                 goto eol;
1115               }
1116             if (announce)
1117               {
1118                 console_putstr (conUnitIdx,  "[auto-input] ");
1119                 announce = 0;
1120               }
1121             csp->autop ++;
1122 
1123             if (c == '\012' || c == '\015')
1124               {
1125 eol:
1126                 if (csp->echo)
1127                   console_putstr (conUnitIdx,  "\r\n");
1128                 sendConsole (conUnitIdx, 04000); // Normal status
1129                 return;
1130               }
1131             else
1132               {
1133                 * csp->tailp ++ = c;
1134                 if (csp->echo)
1135                   console_putchar (conUnitIdx, (char) c);
1136               }
1137           } // for (;;)
1138       } // if (autop)
1139 
1140 //// Read mode and nothing in console buffer
1141 ////   Check for timeout
1142 
1143     if (csp->io_mode == opc_read_mode &&
1144         csp->tailp == csp->keyboardLineBuffer)
1145       {
1146         if (csp->startTime + 30 <= time (NULL))
1147           {
1148             console_putstr (conUnitIdx,  "CONSOLE: TIMEOUT\r\n");
1149             sim_usleep (1000);
1150             csp->readp = csp->keyboardLineBuffer;
1151             csp->tailp = csp->keyboardLineBuffer;
1152             sendConsole (conUnitIdx, 04310); // Null line, status operator
1153                                              // distracted
1154           }
1155       }
1156 
1157 //// Peek at the character in the typeahead buffer
1158 
1159     c = ta_peek ();
1160 
1161     // No data
1162     if (c == SCPE_OK)
1163         return;
1164 
1165     // Convert from scp encoding to ASCII
1166     if (c < SCPE_KFLAG)
1167       {
1168         sim_printf ("impossible %d %o\n", c, c);
1169         return; // Should be impossible
1170       }
1171 
1172     // translate to ascii
1173 
1174     int ch = c - SCPE_KFLAG;
1175 
1176 // XXX This is subject to race conditions
1177     if (csp->io_mode != opc_read_mode)
1178       {
1179         if (ch == '\033' || ch == '\001') // escape or ^A
1180           {
1181             ta_get ();
1182             csp->attn_pressed = true;
1183           }
1184         return;
1185       }
1186 
1187     if (ch == '\177' || ch == '\010')  // backspace/del
1188       {
1189         ta_get ();
1190         if (csp->tailp > csp->keyboardLineBuffer)
1191           {
1192             * csp->tailp = 0;
1193             -- csp->tailp;
1194             if (csp->echo)
1195               console_putstr (conUnitIdx,  "\b \b");
1196           }
1197         return;
1198       }
1199 
1200     if (ch == '\022')  // ^R
1201       {
1202         ta_get ();
1203         if (csp->echo)
1204           {
1205             console_putstr (conUnitIdx,  "^R\r\n");
1206             for (unsigned char * p = csp->keyboardLineBuffer; p < csp->tailp; p ++)
1207               console_putchar (conUnitIdx, (char) (*p));
1208             return;
1209           }
1210       }
1211 
1212     if (ch == '\025')  // ^U
1213       {
1214         ta_get ();
1215         console_putstr (conUnitIdx,  "^U\r\n");
1216         csp->tailp = csp->keyboardLineBuffer;
1217         return;
1218       }
1219 
1220     if (ch == '\030')  // ^X
1221       {
1222         ta_get ();
1223         console_putstr (conUnitIdx,  "^X\r\n");
1224         csp->tailp = csp->keyboardLineBuffer;
1225         return;
1226       }
1227 
1228     if (ch == '\012' || ch == '\015')  // CR/LF
1229       {
1230         ta_get ();
1231         if (csp->echo)
1232           console_putstr (conUnitIdx,  "\r\n");
1233         sendConsole (conUnitIdx, 04000); // Normal status
1234         return;
1235       }
1236 
1237     if (ch == '\033' || ch == '\004' || ch == '\032')  // ESC/^D/^Z
1238       {
1239         ta_get ();
1240         console_putstr (conUnitIdx,  "\r\n");
1241         // Empty input buffer
1242         csp->readp = csp->keyboardLineBuffer;
1243         csp->tailp = csp->keyboardLineBuffer;
1244         sendConsole (conUnitIdx, 04310); // Null line, status operator
1245                                          // distracted
1246         console_putstr (conUnitIdx,  "CONSOLE: RELEASED\n");
1247         sim_usleep (1000);
1248         return;
1249       }
1250 
1251     if (isprint (ch))
1252       {
1253         // silently drop buffer overrun
1254         ta_get ();
1255         if (csp->tailp >= csp->keyboardLineBuffer + sizeof (csp->keyboardLineBuffer))
1256           return;
1257 
1258         * csp->tailp ++ = (unsigned char) ch;
1259         if (csp->echo)
1260           console_putchar (conUnitIdx, (char) ch);
1261         return;
1262       }
1263     // ignore other chars...
1264     ta_get ();
1265     return;
1266   }
1267 
1268 void consoleProcess (void)
     /* [previous][next][first][last][top][bottom][index][help] */
1269   {
1270     for (int conUnitIdx = 0; conUnitIdx < (int) opc_dev.numunits; conUnitIdx ++)
1271       consoleProcessIdx (conUnitIdx);
1272   }
1273 
1274 /*
1275  * opc_iom_cmd ()
1276  *
1277  * Handle a device command.  Invoked by the IOM while processing a PCW
1278  * or IDCW.
1279  */
1280 
1281 iom_cmd_rc_t opc_iom_cmd (uint iomUnitIdx, uint chan) {
     /* [previous][next][first][last][top][bottom][index][help] */
1282   iom_cmd_rc_t rc = IOM_CMD_PROCEED;
1283 
1284 #ifdef LOCKLESS
1285   lock_libuv ();
1286 #endif
1287 
1288   iom_chan_data_t * p = & iom_chan_data[iomUnitIdx][chan];
1289   uint con_unit_idx   = get_ctlr_idx (iomUnitIdx, chan);
1290   UNIT * unitp        = & opc_unit[con_unit_idx];
1291   opc_state_t * csp   = console_state + con_unit_idx;
1292 
1293   p->dev_code = p->IDCW_DEV_CODE;
1294   p->stati = 0;
1295   //int conUnitIdx = (int) d->devUnitIdx;
1296 
1297   // The 6001 only executes the PCW DCW command; the 6601 executes
1298   // the PCW DCW and (at least) the first DCW list item.
1299   // When Multics uses the 6601, the PCW DCW is always 040 RESET.
1300   // The 040 RESET will trigger the DCW list read.
1301   // will change this.
1302 
1303   // IDCW?
1304   if (IS_IDCW (p)) {
1305     // IDCW
1306 
1307     switch (p->IDCW_DEV_CMD) {
1308       case 000: // CMD 00 Request status
1309         sim_debug (DBG_DEBUG, & opc_dev, "%s: Status request\n", __func__);
1310         csp->io_mode = opc_no_mode;
1311         p->stati     = 04000;
1312         break;
1313 
1314       case 003:               // Read BCD
1315         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read BCD echoed\n", __func__);
1316         csp->io_mode = opc_read_mode;
1317         p->recordResidue --;
1318         csp->echo    = true;
1319         csp->bcd     = true;
1320         p->stati     = 04000;
1321         break;
1322 
1323       case 013:               // Write BCD
1324         sim_debug (DBG_DEBUG, & opc_dev, "%s: Write BCD\n", __func__);
1325         p->isRead    = false;
1326         csp->bcd     = true;
1327         csp->io_mode = opc_write_mode;
1328         p->recordResidue --;
1329         p->stati     = 04000;
1330         break;
1331 
1332       case 023:               // Read ASCII
1333         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read ASCII echoed\n", __func__);
1334         csp->io_mode = opc_read_mode;
1335         p->recordResidue --;
1336         csp->echo    = true;
1337         csp->bcd     = false;
1338         p->stati     = 04000;
1339         break;
1340 
1341       case 033:               // Write ASCII
1342         sim_debug (DBG_DEBUG, & opc_dev, "%s: Write ASCII\n", __func__);
1343         p->isRead    = false;
1344         csp->bcd     = false;
1345         csp->io_mode = opc_write_mode;
1346         p->recordResidue --;
1347         p->stati     = 04000;
1348         break;
1349 
1350 // Model 6001 vs. 6601.
1351 //
1352 // When Multics switches to 6601 mode, the PCW DCW is always 040 RESET; the
1353 // bootload and 6001 code never does that. Therefore, we use the 040
1354 // as an indication that the DCW list should be processed.
1355 // All of the other device commands will return IOM_CMD_DISCONNECT, stopping
1356 // parsing of the channel command program. This one will return IOM_CMD_PROCEED,
1357 // causing the parser to move to the DCW list.
1358 
1359       case 040:               // Reset
1360         sim_debug (DBG_DEBUG, & opc_dev, "%s: Reset\n", __func__);
1361         p->stati = 04000;
1362         // T&D probing
1363         //if (p->IDCW_DEV_CODE == 077) {
1364           // T&D uses dev code 77 to test for the console device;
1365           // it ignores dev code, and so returns OK here.
1366           //p->stati = 04502; // invalid device code
1367           // if (p->IDCW_CHAN_CTRL == 0) { sim_warn ("%s: TERMINATE_BUG\n", __func__); return IOM_CMD_DISCONNECT; }
1368         //}
1369         break;
1370 
1371       case 043:               // Read ASCII unechoed
1372         sim_debug (DBG_DEBUG, & tape_dev, "%s: Read ASCII unechoed\n", __func__);
1373         csp->io_mode = opc_read_mode;
1374         p->recordResidue --;
1375         csp->echo    = false;
1376         csp->bcd     = false;
1377         p->stati     = 04000;
1378         break;
1379 
1380       case 051:               // Write Alert -- Ring Bell
1381         sim_debug (DBG_DEBUG, & opc_dev, "%s: Alert\n", __func__);
1382         p->isRead = false;
1383         console_putstr ((int) con_unit_idx,  "CONSOLE: ALERT\r\n");
1384         console_putchar ((int) con_unit_idx, '\a');
1385         p->stati  = 04000;
1386         sim_usleep (1000);
1387         if (csp->model == m6001 && p->isPCW) {
1388           rc = IOM_CMD_DISCONNECT;
1389           goto done;
1390         }
1391         break;
1392 
1393       case 057:               // Read ID (according to AN70-1)
1394         // FIXME: No support for Read ID; appropriate values are not known
1395         //[CAC] Looking at the bootload console code, it seems more
1396         // concerned about the device responding, rather than the actual
1397         // returned value. Make some thing up.
1398         sim_debug (DBG_DEBUG, & opc_dev, "%s: Read ID\n", __func__);
1399         p->stati = 04500;
1400         if (csp->model == m6001 && p->isPCW) {
1401           rc = IOM_CMD_DISCONNECT;
1402           goto done;
1403         }
1404         break;
1405 
1406       case 060:               // LOCK MCA
1407         sim_debug (DBG_DEBUG, & opc_dev, "%s: Lock\n", __func__);
1408         console_putstr ((int) con_unit_idx,  "CONSOLE: LOCK\r\n");
1409         p->stati = 04000;
1410         sim_usleep (1000);
1411         break;
1412 
1413       case 063:               // UNLOCK MCA
1414         sim_debug (DBG_DEBUG, & opc_dev, "%s: Unlock\n", __func__);
1415         console_putstr ((int) con_unit_idx,  "CONSOLE: UNLOCK\r\n");
1416         p->stati = 04000;
1417         sim_usleep (1000);
1418         break;
1419 
1420       default:
1421         sim_debug (DBG_DEBUG, & opc_dev, "%s: Unknown command 0%o\n", __func__, p->IDCW_DEV_CMD);
1422         p->stati = 04501; // command reject, invalid instruction code
1423         rc = IOM_CMD_ERROR;
1424         goto done;
1425     } // switch IDCW_DEV_CMD
1426     goto done;
1427   } // IDCW
1428 
1429   // Not IDCW; TDCW are captured in IOM, so must be IOTD or IOTP
1430   switch (csp->io_mode) {
1431     case opc_no_mode:
1432       sim_warn ("%s: Unexpected IOTx\n", __func__);
1433       rc = IOM_CMD_ERROR;
1434       goto done;
1435 
1436     case opc_read_mode: {
1437         if (csp->tailp != csp->keyboardLineBuffer) {
1438           sim_warn ("%s: Discarding previously buffered input.\n", __func__);
1439         }
1440         uint tally = p->DDCW_TALLY;
1441         uint daddr = p->DDCW_ADDR;
1442 
1443         if (tally == 0) {
1444           tally = 4096;
1445         }
1446 
1447         csp->tailp     = csp->keyboardLineBuffer;
1448         csp->readp     = csp->keyboardLineBuffer;
1449         csp->startTime = time (NULL);
1450         csp->tally     = tally;
1451         csp->daddr     = daddr;
1452         csp->unitp     = unitp;
1453         csp->chan      = (int) chan;
1454 
1455         // If Multics has gone seriously awry (eg crash
1456         // to BCE during boot), the autoinput will become
1457         // wedged waiting for the expect string 'Ready'.
1458         // We just went to read mode; if we are waiting
1459         // on an expect string, it is never coming because
1460         // console access is blocked by the expect code.
1461         // Throw out the script if this happens....
1462 
1463         // If there is autoinput and it is at ^X or ^Y
1464         if (csp->autop && (*csp->autop == 030 || *csp->autop == 031)) { // ^X ^Y
1465           // We are wedged.
1466           // Clear the autoinput buffer; this will cancel the
1467           // expect wait and any remaining script, returning
1468           // control of the console to the user.
1469           // Assuming opc0.
1470           clear_opc_autoinput (ASSUME0, NULL);
1471           ta_flush ();
1472           sim_printf \
1473               ("\r\nScript wedged and abandoned; autoinput and typeahead buffers flushed\r\n");
1474         }
1475         rc = IOM_CMD_PENDING; // command in progress; do not send terminate interrupt
1476         goto done;
1477       } // case opc_read_mode
1478 
1479     case opc_write_mode: {
1480         uint tally = p->DDCW_TALLY;
1481 
1482 // We would hope that number of valid characters in the last word
1483 // would be in DCW_18_20_CP, but it seems to reliably be zero.
1484 
1485         if (tally == 0) {
1486           tally = 4096;
1487         }
1488 
1489         word36 buf[tally];
1490         iom_indirect_data_service (iomUnitIdx, chan, buf, & tally, false);
1491         p->initiate = false;
1492 
1493 
1494 
1495 
1496 
1497 
1498 
1499 
1500 
1501 
1502 
1503 
1504 
1505 
1506 
1507 
1508 
1509 
1510 
1511 
1512 
1513 
1514 
1515 
1516 
1517 
1518 
1519 
1520 
1521 
1522 
1523         // Tally is in words, not chars.
1524         char text[tally * 4 + 1];
1525         char * textp = text;
1526         word36 * bufp = buf;
1527         * textp = 0;
1528 #ifndef __MINGW64__
1529         newlineOff ();
1530 #endif
1531         // 0 == no escape character seen
1532         // 1 ==  ! seen
1533         // 2 == !! seen
1534         int escape_cnt = 0;
1535 
1536         while (tally) {
1537           word36 datum = * bufp ++;
1538           tally --;
1539           if (csp->bcd) {
1540             // Lifted from tolts_util_.pl1:bci_to_ascii
1541             for (int i = 0; i < 6; i ++) {
1542               word36 narrow_char = datum >> 30; // slide leftmost char
1543                                                 //  into low byte
1544               datum = datum << 6; // lose the leftmost char
1545               narrow_char &= 077;
1546               char ch = bcd_code_page [narrow_char];
1547               if (escape_cnt == 2) {
1548                 console_putchar ((int) con_unit_idx, ch);
1549                 * textp ++ = ch;
1550                 escape_cnt = 0;
1551               } else if (ch == '!') {
1552                 escape_cnt ++;
1553               } else if (escape_cnt == 1) {
1554                 uint lp = (uint)narrow_char;
1555                 // !0 is mapped to !1
1556                 // !1 to !9, ![, !#, !@, !;, !>, !?    1 to 15 newlines
1557                 if (lp == 060 /* + */ || lp == 075 /* = */) { // POLTS
1558                   p->stati = 04320;
1559                   goto done;
1560                 }
1561                 if (lp == 0)
1562                   lp = 1;
1563                 if (lp >= 16) {
1564                   console_putstr ((int) con_unit_idx, "\f");
1565                   //* textp ++ = '\f';
1566                 } else {
1567                   for (uint i = 0; i < lp; i ++) {
1568                     console_putstr ((int) con_unit_idx, "\r\n");
1569                     //* textp ++ = '\r';
1570                     //* textp ++ = '\n';
1571                   }
1572                 }
1573                 escape_cnt = 0;
1574               } else if (ch == '?') {
1575                 escape_cnt = 0;
1576               } else {
1577                 console_putchar ((int) con_unit_idx, ch);
1578                 * textp ++ = ch;
1579                 escape_cnt = 0;
1580               }
1581             }
1582           } else {
1583             for (int i = 0; i < 4; i ++) {
1584               word36 wide_char = datum >> 27; // slide leftmost char
1585                                               //  into low byte
1586               datum = datum << 9; // lose the leftmost char
1587               char ch = wide_char & 0x7f;
1588               if (ch != 0177 && ch != 0) {
1589                 console_putchar ((int) con_unit_idx, ch);
1590                 * textp ++ = ch;
1591               }
1592             }
1593           }
1594         }
1595         * textp ++ = 0;
1596 
1597         // autoinput expect
1598         if (csp->autop && * csp->autop == 030) {
1599           //   ^xstring\0
1600           //size_t expl = strlen ((char *) (csp->autop + 1));
1601           //   ^xstring^x
1602           size_t expl = strcspn ((char *) (csp->autop + 1), "\030");
1603 
1604           if (strncmp (text, (char *) (csp->autop + 1), expl) == 0) {
1605             csp->autop += expl + 2;
1606             sim_usleep (1000);
1607             sim_activate (& attn_unit[con_unit_idx], ACTIVATE_1SEC);
1608           }
1609         }
1610         // autoinput expect
1611         if (csp->autop && * csp->autop == 031) {
1612           //   ^ystring\0
1613           //size_t expl = strlen ((char *) (csp->autop + 1));
1614           //   ^ystring^y
1615           size_t expl = strcspn ((char *) (csp->autop + 1), "\031");
1616 
1617           char needle [expl + 1];
1618           strncpy (needle, (char *) csp->autop + 1, expl);
1619           needle [expl] = 0;
1620           if (strstr (text, needle)) {
1621             csp->autop += expl + 2;
1622             sim_usleep (1000);
1623             sim_activate (& attn_unit[con_unit_idx], ACTIVATE_1SEC);
1624           }
1625         }
1626         handleRCP (con_unit_idx, text);
1627 #ifndef __MINGW64__
1628         newlineOn ();
1629 #endif
1630         p->stati = 04000;
1631         goto done;
1632       } // case opc_write_mode
1633   } // switch io_mode
1634 
1635 done:
1636 #ifdef LOCKLESS
1637   unlock_libuv ();
1638 #endif
1639   return rc;
1640 } // opc_iom_cmd
1641 
1642 static t_stat opc_svc (UNIT * unitp)
     /* [previous][next][first][last][top][bottom][index][help] */
1643   {
1644     int con_unit_idx   = (int) OPC_UNIT_IDX (unitp);
1645     uint ctlr_port_num = 0; // Consoles are single ported
1646     uint iom_unit_idx  = cables->opc_to_iom[con_unit_idx][ctlr_port_num].iom_unit_idx;
1647     uint chan_num      = cables->opc_to_iom[con_unit_idx][ctlr_port_num].chan_num;
1648 
1649     opc_iom_cmd (iom_unit_idx, chan_num);
1650     return SCPE_OK;
1651   }
1652 
1653 static t_stat opc_show_nunits (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1654                                  UNUSED int val, UNUSED const void * desc)
1655   {
1656     sim_print ("%d units\n", opc_dev.numunits);
1657     return SCPE_OK;
1658   }
1659 
1660 static t_stat opc_set_nunits (UNUSED UNIT * uptr, int32 UNUSED value,
     /* [previous][next][first][last][top][bottom][index][help] */
1661                                 const char * cptr, UNUSED void * desc)
1662   {
1663     if (! cptr)
1664       return SCPE_ARG;
1665     int n = atoi (cptr);
1666     if (n < 1 || n > N_OPC_UNITS_MAX)
1667       return SCPE_ARG;
1668     opc_dev.numunits = (uint32) n;
1669     return SCPE_OK;
1670   }
1671 
1672 static config_value_list_t cfg_on_off[] =
1673   {
1674     { "off",     0 },
1675     { "on",      1 },
1676     { "disable", 0 },
1677     { "enable",  1 },
1678     { NULL,      0 }
1679   };
1680 
1681 static config_value_list_t cfg_model[] =
1682   {
1683     { "m6001", m6001 },
1684     { "m6004", m6004 },
1685     { "m6601", m6601 },
1686     { NULL,    0 }
1687   };
1688 
1689 static config_list_t opc_config_list[] =
1690   {
1691    { "autoaccept", 0, 1, cfg_on_off },
1692    { "noempty",    0, 1, cfg_on_off },
1693    { "attn_flush", 0, 1, cfg_on_off },
1694    { "model",      1, 0, cfg_model },
1695    { NULL,         0, 0, NULL }
1696   };
1697 
1698 static t_stat opc_set_config (UNUSED UNIT *  uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1699                               const char * cptr, UNUSED void * desc)
1700   {
1701     int devUnitIdx           = (int) OPC_UNIT_IDX (uptr);
1702     opc_state_t * csp        = console_state + devUnitIdx;
1703 // XXX Minor bug; this code doesn't check for trailing garbage
1704     config_state_t cfg_state = { NULL, NULL };
1705 
1706     for (;;)
1707       {
1708         int64_t v;
1709         int rc = cfg_parse (__func__, cptr, opc_config_list,
1710                            & cfg_state, & v);
1711         if (rc == -1) // done
1712           break;
1713 
1714         if (rc == -2) // error
1715           {
1716             cfg_parse_done (& cfg_state);
1717             return SCPE_ARG;
1718           }
1719         const char * p = opc_config_list[rc].name;
1720 
1721         if (strcmp (p, "autoaccept") == 0)
1722           {
1723             csp->autoaccept = (int) v;
1724             continue;
1725           }
1726 
1727         if (strcmp (p, "noempty") == 0)
1728           {
1729             csp->noempty = (int) v;
1730             continue;
1731           }
1732 
1733         if (strcmp (p, "attn_flush") == 0)
1734           {
1735             csp->attn_flush = (int) v;
1736             continue;
1737           }
1738 
1739         if (strcmp (p, "model") == 0)
1740           {
1741             csp->model = (enum console_model) v;
1742             continue;
1743           }
1744 
1745         sim_warn ("error: opc_set_config: Invalid cfg_parse rc <%d>\n",
1746                   rc);
1747         cfg_parse_done (& cfg_state);
1748         return SCPE_ARG;
1749       } // process statements
1750     cfg_parse_done (& cfg_state);
1751     return SCPE_OK;
1752   }
1753 
1754 static t_stat opc_show_config (UNUSED FILE * st, UNUSED UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1755                                UNUSED int  val, UNUSED const void * desc)
1756   {
1757     int devUnitIdx    = (int) OPC_UNIT_IDX (uptr);
1758     opc_state_t * csp = console_state + devUnitIdx;
1759     sim_msg ("flags    : ");
1760     sim_msg ("autoaccept=%d, ", csp->autoaccept);
1761     sim_msg ("noempty=%d, ",    csp->noempty);
1762     sim_msg ("attn_flush=%d",   csp->attn_flush);
1763     return SCPE_OK;
1764   }
1765 
1766 static t_stat opc_show_device_name (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1767                                     UNUSED int val, UNUSED const void * desc)
1768   {
1769     int n = (int) OPC_UNIT_IDX (uptr);
1770     if (n < 0 || n >= N_OPC_UNITS_MAX)
1771       return SCPE_ARG;
1772     sim_printf("name     : OPC%d", n);
1773     return SCPE_OK;
1774   }
1775 
1776 static t_stat opc_set_device_name (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1777                                    const char * cptr, UNUSED void * desc)
1778   {
1779     int n = (int) OPC_UNIT_IDX (uptr);
1780     if (n < 0 || n >= N_OPC_UNITS_MAX)
1781       return SCPE_ARG;
1782     if (cptr)
1783       {
1784         strncpy (console_state[n].device_name, cptr, MAX_DEV_NAME_LEN-1);
1785         console_state[n].device_name[MAX_DEV_NAME_LEN-1] = 0;
1786       }
1787     else
1788       console_state[n].device_name[0] = 0;
1789     return SCPE_OK;
1790   }
1791 
1792 static t_stat opc_set_console_port (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1793                                     const char * cptr, UNUSED void * desc)
1794   {
1795     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1796     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1797       return SCPE_ARG;
1798 
1799     if (cptr)
1800       {
1801         int port = atoi (cptr);
1802         if (port < 0 || port > 65535) // 0 is 'disable'
1803           return SCPE_ARG;
1804         console_state[dev_idx].console_access.port = port;
1805         if (port != 0)
1806           sim_msg ("[OPC emulation: TELNET server port set to %d]\r\n", (int)port);
1807       }
1808     else
1809       console_state[dev_idx].console_access.port = 0;
1810     return SCPE_OK;
1811   }
1812 
1813 static t_stat opc_show_console_port (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1814                                        UNUSED int val, UNUSED const void * desc)
1815   {
1816     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1817     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1818       return SCPE_ARG;
1819     if (console_state[dev_idx].console_access.port)
1820       sim_printf("port     : %d", console_state[dev_idx].console_access.port);
1821     else
1822       sim_printf("port     : disabled");
1823     return SCPE_OK;
1824   }
1825 
1826 static t_stat opc_set_console_address (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1827                                     const char * cptr, UNUSED void * desc)
1828   {
1829     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1830     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1831       return SCPE_ARG;
1832 
1833     if (console_state[dev_idx].console_access.address)
1834       {
1835         FREE (console_state[dev_idx].console_access.address);
1836         console_state[dev_idx].console_access.address = NULL;
1837       }
1838 
1839     if (cptr)
1840       {
1841         console_state[dev_idx].console_access.address = strdup (cptr);
1842         if (!console_state[dev_idx].console_access.address)
1843           {
1844             fprintf (stderr, "\rFATAL: Out of memory! Aborting at %s[%s:%d]\r\n",
1845                      __func__, __FILE__, __LINE__);
1846 #if defined(USE_BACKTRACE)
1847 # ifdef SIGUSR2
1848             (void)raise(SIGUSR2);
1849             /*NOTREACHED*/ /* unreachable */
1850 # endif /* ifdef SIGUSR2 */
1851 #endif /* if defined(USE_BACKTRACE) */
1852             abort();
1853           }
1854         sim_msg ("\r[OPC emulation: OPC%d server address set to %s]\r\n",
1855                 dev_idx, console_state[dev_idx].console_access.address);
1856       }
1857 
1858     return SCPE_OK;
1859   }
1860 
1861 static t_stat opc_show_console_address (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1862                                        UNUSED int val, UNUSED const void * desc)
1863   {
1864     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1865     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1866       return SCPE_ARG;
1867     if (console_state[dev_idx].console_access.address)
1868       sim_printf("address  : %s", console_state[dev_idx].console_access.address);
1869     else
1870       sim_printf("address  : any");
1871     return SCPE_OK;
1872   }
1873 
1874 static t_stat opc_set_console_pw (UNIT * uptr, UNUSED int32 value,
     /* [previous][next][first][last][top][bottom][index][help] */
1875                                     const char * cptr, UNUSED void * desc)
1876   {
1877     long dev_idx = (int) OPC_UNIT_IDX (uptr);
1878     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1879       return SCPE_ARG;
1880 
1881     if (cptr && (strlen(cptr) > 0))
1882       {
1883         char token[strlen (cptr)+1];
1884         int rc = sscanf (cptr, "%s", token);
1885         if (rc != 1)
1886           return SCPE_ARG;
1887         if (strlen (token) > PW_SIZE)
1888           return SCPE_ARG;
1889         strcpy (console_state[dev_idx].console_access.pw, token);
1890       }
1891     else
1892       {
1893         sim_msg ("no password\n");
1894         console_state[dev_idx].console_access.pw[0] = 0;
1895       }
1896 
1897     return SCPE_OK;
1898   }
1899 
1900 static t_stat opc_show_console_pw (UNUSED FILE * st, UNIT * uptr,
     /* [previous][next][first][last][top][bottom][index][help] */
1901                                        UNUSED int val, UNUSED const void * desc)
1902   {
1903     int dev_idx = (int) OPC_UNIT_IDX (uptr);
1904     if (dev_idx < 0 || dev_idx >= N_OPC_UNITS_MAX)
1905       return SCPE_ARG;
1906     sim_printf("password : %s", console_state[dev_idx].console_access.pw);
1907     return SCPE_OK;
1908   }
1909 
1910 static void console_putstr (int conUnitIdx, char * str)
     /* [previous][next][first][last][top][bottom][index][help] */
1911   {
1912     size_t l = strlen (str);
1913     for (size_t i = 0; i < l; i ++)
1914       sim_putchar (str[i]);
1915     opc_state_t * csp = console_state + conUnitIdx;
1916     if (csp->console_access.loggedOn)
1917       accessStartWrite (csp->console_access.client, str,
1918                            (ssize_t) l);
1919   }
1920 
1921 static void consolePutchar0 (int conUnitIdx, char ch) {
     /* [previous][next][first][last][top][bottom][index][help] */
1922   opc_state_t * csp = console_state + conUnitIdx;
1923   sim_putchar (ch);
1924   if (csp->console_access.loggedOn)
1925     accessStartWrite (csp->console_access.client, & ch, 1);
1926 }
1927 
1928 static void console_putchar (int conUnitIdx, char ch) {
     /* [previous][next][first][last][top][bottom][index][help] */
1929   opc_state_t * csp = console_state + conUnitIdx;
1930   if (csp->escapeSequence) { // Prior character was an escape
1931     csp->escapeSequence = false;
1932     if (ch == '1') { // Set tab
1933       if (csp->carrierPosition >= 1 && csp->carrierPosition <= 256) {
1934         csp->tabStops[csp->carrierPosition] = true;
1935       }
1936     } else if (ch == '2') { // Clear all tabs
1937       memset (csp->tabStops, 0, sizeof (csp->tabStops));
1938     } else { // Unrecognized
1939       sim_warn ("Unrecognized escape sequence \\033\\%03o\r\n", ch);
1940     }
1941   } else if (isprint (ch)) {
1942     consolePutchar0 (conUnitIdx, ch);
1943     csp->carrierPosition ++;
1944   } else if (ch == '\t') {  // Tab
1945     while (csp->carrierPosition < bufsize - 1) {
1946       consolePutchar0 (conUnitIdx, ' ');
1947       csp->carrierPosition ++;
1948       if (csp->tabStops[csp->carrierPosition])
1949         break;
1950     }
1951   } else if (ch == '\b') { // Backspace
1952       consolePutchar0 (conUnitIdx, ch);
1953       csp->carrierPosition --;
1954   } else if (ch == '\f' || ch == '\r') { // Formfeed, Carriage return
1955       consolePutchar0 (conUnitIdx, ch);
1956       csp->carrierPosition = 1;
1957   } else if (ch == '\033') { //-V536  // Escape
1958       csp->escapeSequence = true;
1959   } else { // Non-printing and we don't recognize a carriage motion character, so just print it...
1960       consolePutchar0 (conUnitIdx, ch);
1961   }
1962 }
1963 
1964 static void consoleConnectPrompt (uv_tcp_t * client)
     /* [previous][next][first][last][top][bottom][index][help] */
1965   {
1966     accessStartWriteStr (client, "password: \r\n");
1967     uv_access * console_access = (uv_access *) client->data;
1968     console_access->pwPos      = 0;
1969   }
1970 
1971 void startRemoteConsole (void)
     /* [previous][next][first][last][top][bottom][index][help] */
1972   {
1973     for (int conUnitIdx = 0; conUnitIdx < N_OPC_UNITS_MAX; conUnitIdx ++)
1974       {
1975         console_state[conUnitIdx].console_access.connectPrompt = consoleConnectPrompt;
1976         console_state[conUnitIdx].console_access.connected     = NULL;
1977         console_state[conUnitIdx].console_access.useTelnet     = true;
1978 #ifdef CONSOLE_FIX
1979 # ifdef LOCKLESS
1980         lock_libuv ();
1981 # endif
1982 #endif
1983         uv_open_access (& console_state[conUnitIdx].console_access);
1984 #ifdef CONSOLE_FIX
1985 # ifdef LOCKLESS
1986         unlock_libuv ();
1987 # endif
1988 #endif
1989       }
1990   }

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