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