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