1325 lines
40 KiB
C
1325 lines
40 KiB
C
/*
|
||
* besm6_tty.c: BESM-6 teletype device
|
||
*
|
||
* Copyright (c) 2009, Leo Broukhis
|
||
* Copyright (c) 2009, Serge Vakulenko
|
||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||
* copy of this software and associated documentation files (the "Software"),
|
||
* to deal in the Software without restriction, including without limitation
|
||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||
* and/or sell copies of the Software, and to permit persons to whom the
|
||
* Software is furnished to do so, subject to the following conditions:
|
||
|
||
* The above copyright notice and this permission notice shall be included in
|
||
* all copies or substantial portions of the Software.
|
||
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
* SERGE VAKULENKO OR LEONID BROUKHIS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
||
* Except as contained in this notice, the name of Leonid Broukhis or
|
||
* Serge Vakulenko shall not be used in advertising or otherwise to promote
|
||
* the sale, use or other dealings in this Software without prior written
|
||
* authorization from Leonid Broukhis and Serge Vakulenko.
|
||
*/
|
||
|
||
#include "besm6_defs.h"
|
||
#include "sim_sock.h"
|
||
#include "sim_tmxr.h"
|
||
#include <time.h>
|
||
|
||
#define TTY_MAX 24 /* Serial TTY lines */
|
||
#define LINES_MAX TTY_MAX + 2 /* Including parallel "Consul" typewriters */
|
||
/*
|
||
* According to a table in http://ru.wikipedia.org/wiki/МТК-2
|
||
*/
|
||
char * rus[] = { 0, "Т", "\r", "О", " ", "Х", "Н", "М", "\n", "Л", "Р", "Г", "И", "П", "Ц", "Ж",
|
||
"Е", "З", "Д", "Б", "С", "Ы", "Ф", "Ь", "А", "В", "Й", 0, "У", "Я", "К", 0 };
|
||
|
||
char * lat[] = { 0, "T", "\r", "O", " ", "H", "N", "M", "\n", "L", "R", "G", "I", "P", "C", "V",
|
||
"E", "Z", "D", "B", "S", "Y", "F", "X", "A", "W", "J", 0, "U", "Q", "K", 0 };
|
||
|
||
/* $ = Кто там? */
|
||
char * dig[] = { 0, "5", "\r", "9", " ", "Щ", ",", ".", "\n", ")", "4", "Ш", "8", "0", ":", "=",
|
||
"3", "+", "$", "?", "'", "6", "Э", "/", "-", "2", "Ю", 0, "7", "1", "(", 0 };
|
||
|
||
char ** reg = 0;
|
||
|
||
char * process (int sym)
|
||
{
|
||
/* Inversion is required for Baudot TTYs */
|
||
sym ^= 31;
|
||
switch (sym) {
|
||
case 0:
|
||
reg = rus;
|
||
break;
|
||
case 27:
|
||
reg = dig;
|
||
break;
|
||
case 31:
|
||
reg = lat;
|
||
break;
|
||
default:
|
||
return reg[sym];
|
||
}
|
||
return "";
|
||
}
|
||
|
||
/* For serial lines */
|
||
int tty_active [TTY_MAX+1], tty_sym [TTY_MAX+1];
|
||
int tty_typed [TTY_MAX+1], tty_instate [TTY_MAX+1];
|
||
time_t tty_last_time [TTY_MAX+1];
|
||
int tty_idle_count [TTY_MAX+1];
|
||
|
||
/* The serial interrupt generator frequency, common for all VT lines */
|
||
int tty_rate = 300;
|
||
|
||
/* Interrupt generator mode: 1 - model time, 0 - wallclock time */
|
||
int tty_turbo = 1;
|
||
|
||
uint32 vt_sending, vt_receiving;
|
||
uint32 tt_sending, tt_receiving;
|
||
|
||
// Attachments survive the reset
|
||
uint32 tt_mask = 0, vt_mask = 0;
|
||
|
||
uint32 TTY_OUT = 0, TTY_IN = 0, vt_idle = 0;
|
||
uint32 CONSUL_IN[2];
|
||
|
||
uint32 CONS_CAN_PRINT[2] = { 01000, 00400 };
|
||
uint32 CONS_HAS_INPUT[2] = { 04000, 02000 };
|
||
|
||
/* Command line buffers for TELNET mode. */
|
||
char vt_cbuf [CBUFSIZE] [LINES_MAX+1];
|
||
char *vt_cptr [LINES_MAX+1];
|
||
|
||
void tt_print();
|
||
void consul_receive();
|
||
t_stat vt_clk(UNIT *);
|
||
extern const char *get_sim_sw (const char *cptr);
|
||
|
||
int attached_console;
|
||
|
||
UNIT tty_unit [] = {
|
||
{ UDATA (vt_clk, UNIT_DIS|UNIT_IDLE, 0) }, /* fake unit, clock */
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
/* The next two units are parallel interface */
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ UDATA (NULL, UNIT_SEQ, 0) },
|
||
{ 0 }
|
||
};
|
||
|
||
REG tty_reg[] = {
|
||
{ 0 }
|
||
};
|
||
|
||
/*
|
||
* Line descriptors for the TMXR multiplexor.
|
||
* The .conn field contains the socket number and denotes a used line.
|
||
* For local terminals, .conn = 1.
|
||
* To match line indexes with TTY numbers (1-based),
|
||
* line 0 is kept used (.conn = 1).
|
||
* The .rcve field is set to 1 for network connections.
|
||
* For local connections, it is 0.
|
||
*/
|
||
TMLN tty_line [LINES_MAX+1];
|
||
TMXR tty_desc = { LINES_MAX+1, 0, 0, tty_line }; /* mux descriptor */
|
||
|
||
#define TTY_UNICODE_CHARSET 0
|
||
#define TTY_KOI7_JCUKEN_CHARSET (1<<UNIT_V_UF)
|
||
#define TTY_KOI7_QWERTY_CHARSET (2<<UNIT_V_UF)
|
||
#define TTY_CHARSET_MASK (3<<UNIT_V_UF)
|
||
#define TTY_OFFLINE_STATE 0
|
||
#define TTY_TELETYPE_STATE (1<<(UNIT_V_UF+2))
|
||
#define TTY_VT340_STATE (2<<(UNIT_V_UF+2))
|
||
#define TTY_CONSUL_STATE (3<<(UNIT_V_UF+2))
|
||
#define TTY_STATE_MASK (3<<(UNIT_V_UF+2))
|
||
#define TTY_DESTRUCTIVE_BSPACE 0
|
||
#define TTY_AUTHENTIC_BSPACE (1<<(UNIT_V_UF+4))
|
||
#define TTY_BSPACE_MASK (1<<(UNIT_V_UF+4))
|
||
#define TTY_CMDLINE_MASK (1<<(UNIT_V_UF+5))
|
||
|
||
t_stat tty_reset (DEVICE *dptr)
|
||
{
|
||
memset(tty_active, 0, sizeof(tty_active));
|
||
memset(tty_sym, 0, sizeof(tty_sym));
|
||
memset(tty_typed, 0, sizeof(tty_typed));
|
||
memset(tty_instate, 0, sizeof(tty_instate));
|
||
vt_sending = vt_receiving = 0;
|
||
TTY_IN = TTY_OUT = 0;
|
||
CONSUL_IN[0] = CONSUL_IN[1] = 0;
|
||
reg = rus;
|
||
vt_idle = 1;
|
||
tty_line[0].conn = 1; /* faked, always busy */
|
||
/* In the READY2 register the ready flag for typewriters is inverted (0 means ready),
|
||
* and the device is always ready. */
|
||
/* Forcing a ready interrupt. */
|
||
PRP |= CONS_CAN_PRINT[0] | CONS_CAN_PRINT[1];
|
||
// Schedule the very first TTY interrupt to match the next clock interrupt.
|
||
return sim_clock_coschedule (tty_unit, 0);
|
||
}
|
||
|
||
/* Bit 19 of GRP, should be <tty_rate> Hz */
|
||
t_stat vt_clk (UNIT * this)
|
||
{
|
||
int num;
|
||
|
||
GRP |= MGRP & GRP_SERIAL;
|
||
|
||
/* Polling receiving from sockets */
|
||
tmxr_poll_rx (&tty_desc);
|
||
|
||
vt_print();
|
||
vt_receive();
|
||
consul_receive();
|
||
|
||
/* Are there any new network connections? */
|
||
num = tmxr_poll_conn (&tty_desc);
|
||
if (num > 0 && num <= LINES_MAX) {
|
||
char buf [80];
|
||
TMLN *t = &tty_line [num];
|
||
besm6_debug ("*** tty%d: a new connection from %s",
|
||
num, t->ipad);
|
||
t->rcve = 1;
|
||
tty_unit[num].flags &= ~TTY_STATE_MASK;
|
||
tty_unit[num].flags |= TTY_VT340_STATE;
|
||
if (num <= TTY_MAX)
|
||
vt_mask |= 1 << (TTY_MAX - num);
|
||
|
||
switch (tty_unit[num].flags & TTY_CHARSET_MASK) {
|
||
case TTY_KOI7_JCUKEN_CHARSET:
|
||
tmxr_linemsg (t, "Encoding is KOI-7 (jcuken)\r\n");
|
||
break;
|
||
case TTY_KOI7_QWERTY_CHARSET:
|
||
tmxr_linemsg (t, "Encoding is KOI-7 (qwerty)\r\n");
|
||
break;
|
||
case TTY_UNICODE_CHARSET:
|
||
tmxr_linemsg (t, "Encoding is UTF-8\r\n");
|
||
break;
|
||
}
|
||
tty_idle_count[num] = 0;
|
||
tty_last_time[num] = time (0);
|
||
sprintf (buf, "%.24s from %s\r\n",
|
||
ctime (&tty_last_time[num]),
|
||
t->ipad);
|
||
tmxr_linemsg (t, buf);
|
||
|
||
/* Entering ^C (ETX) to get a prompt. */
|
||
t->rxb [t->rxbpi++] = '\3';
|
||
}
|
||
|
||
/*
|
||
* It the operator console is remote, we still need to probe the local keyboard
|
||
* for a WRU, say, 10 times a second.
|
||
*/
|
||
if (!attached_console) {
|
||
static int divider;
|
||
if (++divider == CLK_TPS/10) {
|
||
divider = 0;
|
||
if (SCPE_STOP == sim_poll_kbd())
|
||
stop_cpu = 1;
|
||
}
|
||
}
|
||
|
||
/* Polling sockets for transmission. */
|
||
tmxr_poll_tx (&tty_desc);
|
||
/* If the TTY system is not idle, schedule the next interrupt
|
||
* by instruction count using the target interrupt rate of 300 Hz;
|
||
* otherwise we can wait for a roughly equivalent wallclock time period,
|
||
* e.g. until the next 250 Hz wallclock interrupt, but making sure
|
||
* that the model time interval between GRP_SERIAL interrupts
|
||
* is never less than expected.
|
||
*/
|
||
if (vt_is_idle()) {
|
||
/* When idle, slow down the TTY interrupts to match the clock interrupts. */
|
||
return sim_clock_coschedule (this, 0);
|
||
} else if (tty_turbo) {
|
||
/* In "turbo" mode, the TTY works at the model speed */
|
||
return sim_activate(this, 1000*MSEC/tty_rate);
|
||
} else {
|
||
/* In "non-turbo" mode, the TTY interrupts imitate the true feel of the speed */
|
||
return sim_activate_after(this, 1000*MSEC/tty_rate);
|
||
}
|
||
}
|
||
|
||
t_stat tty_setmode (UNIT *u, int32 val, CONST char *cptr, void *desc)
|
||
{
|
||
int num = u - tty_unit;
|
||
TMLN *t = &tty_line [num];
|
||
uint32 mask = 1 << (TTY_MAX - num);
|
||
|
||
switch (val & TTY_STATE_MASK) {
|
||
case TTY_OFFLINE_STATE:
|
||
if (t->conn) {
|
||
if (t->rcve) {
|
||
tmxr_reset_ln (t);
|
||
t->rcve = 0;
|
||
} else
|
||
t->conn = 0;
|
||
if (num <= TTY_MAX) {
|
||
tty_sym[num] =
|
||
tty_active[num] =
|
||
tty_typed[num] =
|
||
tty_instate[num] = 0;
|
||
vt_mask &= ~mask;
|
||
tt_mask &= ~mask;
|
||
}
|
||
}
|
||
break;
|
||
case TTY_TELETYPE_STATE:
|
||
if (num > TTY_MAX)
|
||
return SCPE_NXPAR;
|
||
t->conn = 1;
|
||
t->rcve = 0;
|
||
tt_mask |= mask;
|
||
vt_mask &= ~mask;
|
||
break;
|
||
case TTY_VT340_STATE:
|
||
t->conn = 1;
|
||
t->rcve = 0;
|
||
if (num <= TTY_MAX) {
|
||
vt_mask |= mask;
|
||
tt_mask &= ~mask;
|
||
}
|
||
break;
|
||
case TTY_CONSUL_STATE:
|
||
if (num <= TTY_MAX)
|
||
return SCPE_NXPAR;
|
||
t->conn = 1;
|
||
t->rcve = 0;
|
||
break;
|
||
}
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/*
|
||
* Allowing telnet connections is done with
|
||
* attach tty <port>
|
||
* Where <port> is the port number for telnet, e.g. 4199.
|
||
*/
|
||
t_stat tty_attach (UNIT *u, CONST char *cptr)
|
||
{
|
||
int num = u - tty_unit;
|
||
char gbuf[CBUFSIZE];
|
||
int r, m, n;
|
||
|
||
/* All arguments but the magic words "console" and "none" are passed
|
||
* to tmxr_attach().
|
||
*/
|
||
get_glyph (cptr, gbuf, 0);
|
||
/* Disallowing future connections to a line */
|
||
if (strcmp (gbuf, "NONE") == 0) {
|
||
/* Marking the TTY as unusable. */
|
||
tty_line[num].conn = 1;
|
||
tty_line[num].rcve = 0;
|
||
if (num <= TTY_MAX) {
|
||
vt_mask &= ~(1 << (TTY_MAX - num));
|
||
tt_mask &= ~(1 << (TTY_MAX - num));
|
||
}
|
||
besm6_debug ("*** turning off T%03o", num);
|
||
return SCPE_OK;
|
||
}
|
||
if (strcmp (gbuf, "CONSOLE")) {
|
||
/* Saving and restoring all .conn,
|
||
* because tmxr_attach() zeroes them. */
|
||
for (m=0, n=1; n<=LINES_MAX; ++n)
|
||
if (tty_line[n].conn)
|
||
m |= 1 << (LINES_MAX-n);
|
||
/* The unit number is ignored for the port assignment */
|
||
r = tmxr_attach (&tty_desc, &tty_unit[0], cptr);
|
||
for (n=1; n<=LINES_MAX; ++n)
|
||
if (m >> (LINES_MAX-n) & 1)
|
||
tty_line[n].conn = 1;
|
||
return r;
|
||
} else {
|
||
/* Attaching SIMH console to a particular terminal. */
|
||
u->flags &= ~TTY_STATE_MASK;
|
||
u->flags |= TTY_VT340_STATE;
|
||
tty_line[num].conn = 1;
|
||
tty_line[num].rcve = 0;
|
||
if (num <= TTY_MAX)
|
||
vt_mask |= 1 << (TTY_MAX - num);
|
||
besm6_debug ("*** console on T%03o", num);
|
||
attached_console = 1;
|
||
return SCPE_OK;
|
||
}
|
||
return SCPE_ALATT;
|
||
}
|
||
|
||
t_stat tty_detach (UNIT *u)
|
||
{
|
||
return tmxr_detach (&tty_desc, &tty_unit[0]);
|
||
}
|
||
|
||
t_stat tty_showrate (FILE *f, UNIT *up, int32 v, CONST void *dp) {
|
||
fprintf(f, "%d Baud", tty_rate);
|
||
return SCPE_OK;
|
||
}
|
||
|
||
t_stat tty_showturbo (FILE *f, UNIT *up, int32 v, CONST void *dp) {
|
||
fprintf(f, tty_turbo ? "Turbo" : "Authentic feel");
|
||
return SCPE_OK;
|
||
}
|
||
|
||
t_stat tty_setrate (UNIT *up, int32 v, CONST char *cp, void *dp) {
|
||
int rate;
|
||
if (cp)
|
||
rate = atoi(cp);
|
||
else
|
||
return SCPE_MISVAL;
|
||
if (rate <= 0 || rate > 19200 || rate % 300 != 0)
|
||
return SCPE_ARG;
|
||
rate /= 300;
|
||
if ((rate-1) & rate)
|
||
return SCPE_ARG;
|
||
tty_rate = rate * 300;
|
||
return SCPE_OK;
|
||
}
|
||
|
||
t_stat tty_setturbo (UNIT *up, int32 v, CONST char *cp, void *dp) {
|
||
if (!cp)
|
||
return SCPE_MISVAL;
|
||
if (!sim_strcasecmp(cp, "ON"))
|
||
tty_turbo = 1;
|
||
else if (!sim_strcasecmp(cp, "OFF"))
|
||
tty_turbo = 0;
|
||
else
|
||
return SCPE_ARG;
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/*
|
||
* TTY control:
|
||
* set ttyN unicode - selecting UTF-8 encoding
|
||
* set ttyN jcuken - selecting KOI-7 encoding, JCUKEN layout
|
||
* set ttyN qwerty - selecting KOI-7 encoding, QWERTY layout
|
||
* set ttyN off - disconnecting a line
|
||
* set ttyN tt - a Baudot TTY
|
||
* set ttyN vt - a Videoton-340 terminal
|
||
* set ttyN consul - a "Consul-254" typewriter
|
||
* set ttyN destrbs - destructive (erasing) backspace
|
||
* set ttyN authbs - authentic backspace (cursor left)
|
||
* set tty disconnect=N - forceful termination of a telnet connection
|
||
* set tty rate=N - I/O rate in Hz
|
||
* set tty turbo={ON,OFF} - TTY interrupts use model time (on) or wallclock (0ff)
|
||
* show tty - showing modes and types
|
||
* show tty connections - showing IP-addresses and connection times
|
||
* show tty statistics - showing TX/RX byte counts
|
||
*/
|
||
MTAB tty_mod[] = {
|
||
{ TTY_CHARSET_MASK, TTY_UNICODE_CHARSET, "UTF-8 input",
|
||
"UNICODE" },
|
||
{ TTY_CHARSET_MASK, TTY_KOI7_JCUKEN_CHARSET, "KOI7 (jcuken) input",
|
||
"JCUKEN" },
|
||
{ TTY_CHARSET_MASK, TTY_KOI7_QWERTY_CHARSET, "KOI7 (qwerty) input",
|
||
"QWERTY" },
|
||
{ TTY_STATE_MASK, TTY_OFFLINE_STATE, "offline",
|
||
"OFF", &tty_setmode },
|
||
{ TTY_STATE_MASK, TTY_TELETYPE_STATE, "Teletype",
|
||
"TT", &tty_setmode },
|
||
{ TTY_STATE_MASK, TTY_VT340_STATE, "Videoton-340",
|
||
"VT", &tty_setmode },
|
||
{ TTY_STATE_MASK, TTY_CONSUL_STATE, "Consul-254",
|
||
"CONSUL", &tty_setmode },
|
||
{ TTY_BSPACE_MASK, TTY_DESTRUCTIVE_BSPACE, "destructive backspace",
|
||
"DESTRBS" },
|
||
{ TTY_BSPACE_MASK, TTY_AUTHENTIC_BSPACE, NULL,
|
||
"AUTHBS" },
|
||
{ MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, NULL,
|
||
"DISCONNECT", &tmxr_dscln, NULL, (void*) &tty_desc, "terminates telnet connection" },
|
||
{ MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, "RATE",
|
||
"RATE", &tty_setrate, &tty_showrate, NULL, "{300,600,1200,2400,4800,9600,19200}" },
|
||
{ MTAB_XTD | MTAB_VDV | MTAB_VALR, 1, "TURBO",
|
||
"TURBO", &tty_setturbo, &tty_showturbo, NULL, "{ON, OFF}"},
|
||
{ UNIT_ATT, UNIT_ATT, "connections",
|
||
NULL, NULL, &tmxr_show_summ, (void*) &tty_desc },
|
||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 1, "CONNECTIONS",
|
||
NULL, NULL, &tmxr_show_cstat, (void*) &tty_desc },
|
||
{ MTAB_XTD | MTAB_VDV | MTAB_NMO, 0, "STATISTICS",
|
||
NULL, NULL, &tmxr_show_cstat, (void*) &tty_desc },
|
||
{ MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL,
|
||
"LOG", &tmxr_set_log, &tmxr_show_log, (void*) &tty_desc },
|
||
{ MTAB_XTD | MTAB_VUN | MTAB_NC, 0, NULL,
|
||
"NOLOG", &tmxr_set_nolog, NULL, (void*) &tty_desc },
|
||
{ 0 }
|
||
};
|
||
|
||
DEVICE tty_dev = {
|
||
"TTY", tty_unit, tty_reg, tty_mod,
|
||
27, 2, 1, 1, 2, 1,
|
||
NULL, NULL, &tty_reset, NULL, &tty_attach, &tty_detach,
|
||
NULL, DEV_NET|DEV_DEBUG
|
||
};
|
||
|
||
void tty_send (uint32 mask)
|
||
{
|
||
/* besm6_debug ("*** TTY: transmit %08o", mask); */
|
||
|
||
TTY_OUT = mask;
|
||
}
|
||
|
||
/*
|
||
* Sending a character to a terminal with the given number.
|
||
*/
|
||
void vt_putc (int num, int c)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
|
||
if (! t->conn)
|
||
return;
|
||
if (t->rcve) {
|
||
/* A telnet connection. */
|
||
tmxr_putc_ln (t, c);
|
||
} else {
|
||
/* Console output. */
|
||
sim_putchar(c);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Sending a string to a terminal with the given number.
|
||
*/
|
||
void vt_puts (int num, const char *s)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
|
||
if (! t->conn)
|
||
return;
|
||
if (t->rcve) {
|
||
/* A telnet connection. */
|
||
tmxr_linemsg (t, s);
|
||
} else {
|
||
/* Console output. */
|
||
while (*s) sim_putchar(*s++);
|
||
}
|
||
}
|
||
|
||
const char * koi7_rus_to_unicode [32] = {
|
||
"Ю", "А", "Б", "Ц", "Д", "Е", "Ф", "Г",
|
||
"Х", "И", "Й", "К", "Л", "М", "Н", "О",
|
||
"П", "Я", "Р", "С", "Т", "У", "Ж", "В",
|
||
"Ь", "Ы", "З", "Ш", "Э", "Щ", "Ч", "\0x7f",
|
||
};
|
||
|
||
/* Videoton-340 employed single byte control codes rather than ESC sequences. */
|
||
void vt_send(int num, uint32 sym, int destructive_bs)
|
||
{
|
||
if (sym < 0x60) {
|
||
switch (sym) {
|
||
case '\031':
|
||
/* Up */
|
||
vt_puts (num, "\033[");
|
||
sym = 'A';
|
||
break;
|
||
case '\032':
|
||
/* Down */
|
||
vt_puts (num, "\033[");
|
||
sym = 'B';
|
||
break;
|
||
case '\030':
|
||
/* Right */
|
||
vt_puts (num, "\033[");
|
||
sym = 'C';
|
||
break;
|
||
case '\b':
|
||
/* Left */
|
||
vt_puts (num, "\033[");
|
||
if (destructive_bs) {
|
||
/* Erasing the previous char. */
|
||
vt_puts (num, "D \033[");
|
||
}
|
||
sym = 'D';
|
||
break;
|
||
case '\v':
|
||
case '\033':
|
||
case '\0':
|
||
/* Sending the actual char */
|
||
break;
|
||
case '\037':
|
||
/* Clear screen */
|
||
vt_puts (num, "\033[H\033[");
|
||
sym = 'J';
|
||
break;
|
||
case '\n':
|
||
/* Also does carriage return */
|
||
vt_putc (num, '\r');
|
||
sym = '\n';
|
||
break;
|
||
case '\f':
|
||
/* Home */
|
||
vt_puts(num, "\033[");
|
||
sym = 'H';
|
||
break;
|
||
case '\r':
|
||
case '\003':
|
||
/* Not displayed */
|
||
sym = 0;
|
||
break;
|
||
default:
|
||
if (sym < ' ') {
|
||
/* Other control chars were displayed as dimmed. */
|
||
vt_puts (num, "\033[2m");
|
||
vt_putc (num, sym | 0x40);
|
||
vt_puts (num, "\033[");
|
||
/* Terminating the ESC sequence */
|
||
sym = 'm';
|
||
}
|
||
}
|
||
if (sym)
|
||
vt_putc (num, sym);
|
||
} else
|
||
vt_puts (num, koi7_rus_to_unicode[sym - 0x60]);
|
||
}
|
||
|
||
/*
|
||
* Handling output to all connected terminals.
|
||
*/
|
||
void vt_print()
|
||
{
|
||
uint32 workset = (TTY_OUT & vt_mask) | vt_sending;
|
||
int num;
|
||
|
||
if (workset == 0) {
|
||
++vt_idle;
|
||
return;
|
||
}
|
||
for (num = besm6_highest_bit (workset) - TTY_MAX;
|
||
workset; num = besm6_highest_bit (workset) - TTY_MAX) {
|
||
int mask = 1 << (TTY_MAX - num);
|
||
int c = (TTY_OUT & mask) != 0;
|
||
switch (tty_active[num]*2+c) {
|
||
case 0: /* idle */
|
||
besm6_debug ("Warning: inactive ttys should have been screened");
|
||
continue;
|
||
case 1: /* start bit */
|
||
vt_sending |= mask;
|
||
tty_active[num] = 1;
|
||
break;
|
||
case 18: /* stop bit */
|
||
tty_sym[num] = ~tty_sym[num] & 0x7f;
|
||
vt_send (num, tty_sym[num],
|
||
(tty_unit[num].flags & TTY_BSPACE_MASK) == TTY_DESTRUCTIVE_BSPACE);
|
||
tty_active[num] = 0;
|
||
tty_sym[num] = 0;
|
||
vt_sending &= ~mask;
|
||
break;
|
||
case 19: /* framing error */
|
||
vt_putc (num, '#');
|
||
break;
|
||
default:
|
||
/* little endian ordering */
|
||
if (c) {
|
||
tty_sym[num] |= 1 << (tty_active[num]-1);
|
||
}
|
||
++tty_active[num];
|
||
break;
|
||
}
|
||
workset &= ~mask;
|
||
}
|
||
vt_idle = 0;
|
||
}
|
||
|
||
/* Input from Baudot TTYs not implemented. Output may require some additional work.
|
||
*/
|
||
void tt_print()
|
||
{
|
||
uint32 workset = (TTY_OUT & tt_mask) | tt_sending;
|
||
int num;
|
||
|
||
if (workset == 0) {
|
||
return;
|
||
}
|
||
|
||
for (num = besm6_highest_bit (workset) - TTY_MAX;
|
||
workset; num = besm6_highest_bit (workset) - TTY_MAX) {
|
||
int mask = 1 << (TTY_MAX - num);
|
||
int c = (TTY_OUT & mask) != 0;
|
||
switch (tty_active[num]*2+c) {
|
||
case 0: /* idle */
|
||
break;
|
||
case 1: /* start bit */
|
||
tt_sending |= mask;
|
||
tty_active[num] = 1;
|
||
break;
|
||
case 12: /* stop bit */
|
||
vt_puts (num, process (tty_sym[num]));
|
||
tty_active[num] = 0;
|
||
tty_sym[num] = 0;
|
||
tt_sending &= ~mask;
|
||
break;
|
||
case 13: /* framing error */
|
||
vt_putc (num, '#');
|
||
break;
|
||
default:
|
||
/* big endian ordering */
|
||
if (c) {
|
||
tty_sym[num] |= 1 << (5-tty_active[num]);
|
||
}
|
||
++tty_active[num];
|
||
break;
|
||
}
|
||
workset &= ~mask;
|
||
}
|
||
vt_idle = 0;
|
||
}
|
||
|
||
/*
|
||
* Converting from Unicode to KOI-7.
|
||
* Returns -1 if unsuccessful.
|
||
*/
|
||
static int unicode_to_koi7 (unsigned val)
|
||
{
|
||
if (val <= '_') return val;
|
||
else if ('a' <= val && val <= 'z') return val + 'Z' - 'z';
|
||
else switch (val) {
|
||
case 0x007f: return 0x7f;
|
||
case 0x0410: case 0x0430: return 0x61;
|
||
case 0x0411: case 0x0431: return 0x62;
|
||
case 0x0412: case 0x0432: return 0x77;
|
||
case 0x0413: case 0x0433: return 0x67;
|
||
case 0x0414: case 0x0434: return 0x64;
|
||
case 0x0415: case 0x0435: return 0x65;
|
||
case 0x0416: case 0x0436: return 0x76;
|
||
case 0x0417: case 0x0437: return 0x7a;
|
||
case 0x0418: case 0x0438: return 0x69;
|
||
case 0x0419: case 0x0439: return 0x6a;
|
||
case 0x041a: case 0x043a: return 0x6b;
|
||
case 0x041b: case 0x043b: return 0x6c;
|
||
case 0x041c: case 0x043c: return 0x6d;
|
||
case 0x041d: case 0x043d: return 0x6e;
|
||
case 0x041e: case 0x043e: return 0x6f;
|
||
case 0x041f: case 0x043f: return 0x70;
|
||
case 0x0420: case 0x0440: return 0x72;
|
||
case 0x0421: case 0x0441: return 0x73;
|
||
case 0x0422: case 0x0442: return 0x74;
|
||
case 0x0423: case 0x0443: return 0x75;
|
||
case 0x0424: case 0x0444: return 0x66;
|
||
case 0x0425: case 0x0445: return 0x68;
|
||
case 0x0426: case 0x0446: return 0x63;
|
||
case 0x0427: case 0x0447: return 0x7e;
|
||
case 0x0428: case 0x0448: return 0x7b;
|
||
case 0x0429: case 0x0449: return 0x7d;
|
||
case 0x042b: case 0x044b: return 0x79;
|
||
case 0x042c: case 0x044c: return 0x78;
|
||
case 0x042d: case 0x044d: return 0x7c;
|
||
case 0x042e: case 0x044e: return 0x60;
|
||
case 0x042f: case 0x044f: return 0x71;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
/*
|
||
* Set command
|
||
*/
|
||
static t_stat cmd_set (int32 num, CONST char *cptr)
|
||
{
|
||
char gbuf [CBUFSIZE];
|
||
int len;
|
||
|
||
cptr = (CONST char *)get_sim_sw (cptr);
|
||
if (! cptr)
|
||
return SCPE_INVSW;
|
||
if (! *cptr)
|
||
return SCPE_NOPARAM;
|
||
cptr = get_glyph (cptr, gbuf, 0);
|
||
if (*cptr)
|
||
return SCPE_2MARG;
|
||
|
||
len = strlen (gbuf);
|
||
if (strncmp ("UNICODE", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_CHARSET_MASK;
|
||
tty_unit[num].flags |= TTY_UNICODE_CHARSET;
|
||
} else if (strncmp ("JCUKEN", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_CHARSET_MASK;
|
||
tty_unit[num].flags |= TTY_KOI7_JCUKEN_CHARSET;
|
||
} else if (strncmp ("QWERTY", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_CHARSET_MASK;
|
||
tty_unit[num].flags |= TTY_KOI7_QWERTY_CHARSET;
|
||
} else if (strncmp ("TT", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_STATE_MASK;
|
||
tty_unit[num].flags |= TTY_TELETYPE_STATE;
|
||
} else if (strncmp ("VT", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_STATE_MASK;
|
||
tty_unit[num].flags |= TTY_VT340_STATE;
|
||
} else if (strncmp ("CONSUL", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_STATE_MASK;
|
||
tty_unit[num].flags |= TTY_CONSUL_STATE;
|
||
} else if (strncmp ("DESTRBS", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_BSPACE_MASK;
|
||
tty_unit[num].flags |= TTY_DESTRUCTIVE_BSPACE;
|
||
} else if (strncmp ("AUTHBS", gbuf, len) == 0) {
|
||
tty_unit[num].flags &= ~TTY_BSPACE_MASK;
|
||
tty_unit[num].flags |= TTY_AUTHENTIC_BSPACE;
|
||
} else {
|
||
return SCPE_NXPAR;
|
||
}
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/*
|
||
* Show command
|
||
*/
|
||
static t_stat cmd_show (int32 num, CONST char *cptr)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
char gbuf [CBUFSIZE];
|
||
MTAB *m;
|
||
int len;
|
||
|
||
cptr = (CONST char *)get_sim_sw (cptr);
|
||
if (! cptr)
|
||
return SCPE_INVSW;
|
||
if (! *cptr) {
|
||
sprintf (gbuf, "TTY%d", num);
|
||
tmxr_linemsg (t, gbuf);
|
||
for (m=tty_mod; m->mask; m++) {
|
||
if (m->pstring &&
|
||
(tty_unit[num].flags & m->mask) == m->match) {
|
||
tmxr_linemsg (t, ", ");
|
||
tmxr_linemsg (t, m->pstring);
|
||
}
|
||
}
|
||
if (t->txlog)
|
||
tmxr_linemsg (t, ", log");
|
||
tmxr_linemsg (t, "\r\n");
|
||
return SCPE_OK;
|
||
}
|
||
cptr = get_glyph (cptr, gbuf, 0);
|
||
if (*cptr)
|
||
return SCPE_2MARG;
|
||
|
||
len = strlen (gbuf);
|
||
if (strncmp ("STATISTICS", gbuf, len) == 0) {
|
||
sprintf (gbuf, "line %d: input queued/total = %d/%d, "
|
||
"output queued/total = %d/%d\r\n", num,
|
||
t->rxbpi - t->rxbpr, t->rxcnt,
|
||
t->txbpi - t->txbpr, t->txcnt);
|
||
tmxr_linemsg (t, gbuf);
|
||
} else {
|
||
return SCPE_NXPAR;
|
||
}
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/*
|
||
* Exit command
|
||
*/
|
||
static t_stat cmd_exit (int32 num, CONST char *cptr)
|
||
{
|
||
return SCPE_EXIT;
|
||
}
|
||
|
||
static t_stat cmd_help (int32 num, CONST char *cptr);
|
||
|
||
static CTAB cmd_table[] = {
|
||
{ "SET", &cmd_set, 0,
|
||
"set unicode select UTF-8 encoding\r\n"
|
||
"set jcuken select KOI7 encoding, 'jcuken' keymap\r\n"
|
||
"set qwerty select KOI7 encoding, 'qwerty' keymap\r\n"
|
||
"set tt use Teletype mode\r\n"
|
||
"set vt use Videoton-340 mode\r\n"
|
||
"set consul use Consul-254 mode\r\n"
|
||
"set destrbs destructive backspace\r\n"
|
||
"set authbs authentic backspace\r\n"
|
||
},
|
||
{ "SHOW", &cmd_show, 0,
|
||
"sh{ow} show modes of the terminal\r\n"
|
||
"sh{ow} s{tatistics} show network statistics\r\n"
|
||
},
|
||
{ "EXIT", &cmd_exit, 0,
|
||
"exi{t} | q{uit} | by{e} exit from simulation\r\n"
|
||
},
|
||
{ "QUIT", &cmd_exit, 0, NULL
|
||
},
|
||
{ "BYE", &cmd_exit, 0, NULL
|
||
},
|
||
{ "HELP", &cmd_help, 0,
|
||
"h{elp} type this message\r\n"
|
||
"h{elp} <command> type help for command\r\n"
|
||
},
|
||
{ 0 }
|
||
};
|
||
|
||
/*
|
||
* Find command routine
|
||
*/
|
||
static CTAB *lookup_cmd (char *command)
|
||
{
|
||
CTAB *c;
|
||
int len;
|
||
|
||
len = strlen (command);
|
||
for (c=cmd_table; c->name; c++) {
|
||
if (strncmp (command, c->name, len) == 0)
|
||
return c;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Help command
|
||
*/
|
||
static t_stat cmd_help (int32 num, CONST char *cptr)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
char gbuf [CBUFSIZE];
|
||
CTAB *c;
|
||
|
||
cptr = (CONST char *)get_sim_sw (cptr);
|
||
if (! cptr)
|
||
return SCPE_INVSW;
|
||
if (! *cptr) {
|
||
/* Listing all commands. */
|
||
tmxr_linemsg (t, "Commands may be abbreviated. Commands are:\r\n\r\n");
|
||
for (c=cmd_table; c && c->name; c++)
|
||
if (c->help)
|
||
tmxr_linemsg (t, c->help);
|
||
return SCPE_OK;
|
||
}
|
||
cptr = get_glyph (cptr, gbuf, 0);
|
||
if (*cptr)
|
||
return SCPE_2MARG;
|
||
c = lookup_cmd (gbuf);
|
||
if (! c)
|
||
return SCPE_ARG;
|
||
/* Describing a command. */
|
||
tmxr_linemsg (t, c->help);
|
||
return SCPE_OK;
|
||
}
|
||
|
||
/*
|
||
* Executing a command.
|
||
*/
|
||
void vt_cmd_exec (int num)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
char gbuf [CBUFSIZE];
|
||
CONST char *cptr;
|
||
CTAB *cmdp;
|
||
t_stat err;
|
||
extern char *scp_errors[];
|
||
|
||
cptr = get_glyph (vt_cbuf [num], gbuf, 0); /* get command glyph */
|
||
cmdp = lookup_cmd (gbuf); /* lookup command */
|
||
if (! cmdp) {
|
||
tmxr_linemsg (t, scp_errors[SCPE_UNK - SCPE_BASE]);
|
||
tmxr_linemsg (t, "\r\n");
|
||
return;
|
||
}
|
||
err = cmdp->action (num, cptr); /* if found, exec */
|
||
if (err >= SCPE_BASE) { /* error? */
|
||
tmxr_linemsg (t, scp_errors [err - SCPE_BASE]);
|
||
tmxr_linemsg (t, "\r\n");
|
||
}
|
||
if (err == SCPE_EXIT) { /* close telnet session */
|
||
tmxr_reset_ln (t);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Command line interface mode.
|
||
*/
|
||
void vt_cmd_loop (int num, int c)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
char *cbuf, **cptr;
|
||
|
||
cbuf = vt_cbuf [num];
|
||
cptr = &vt_cptr [num];
|
||
|
||
switch (c) {
|
||
case '\r':
|
||
case '\n':
|
||
tmxr_linemsg (t, "\r\n");
|
||
if (*cptr <= cbuf) {
|
||
/* An empty line - returning to terminal emulation. */
|
||
tty_unit[num].flags &= ~TTY_CMDLINE_MASK;
|
||
break;
|
||
}
|
||
/* Executing. */
|
||
**cptr = 0;
|
||
vt_cmd_exec (num);
|
||
tmxr_linemsg (t, "sim>");
|
||
*cptr = vt_cbuf[num];
|
||
break;
|
||
case '\b':
|
||
case 0177:
|
||
/* Backspace. */
|
||
if (*cptr <= cbuf)
|
||
break;
|
||
tmxr_linemsg (t, "\b \b");
|
||
while (*cptr > cbuf) {
|
||
--*cptr;
|
||
if (! (**cptr & 0x80))
|
||
break;
|
||
}
|
||
break;
|
||
case 'U' & 037:
|
||
/* Erase line. */
|
||
erase_line: while (*cptr > cbuf) {
|
||
--*cptr;
|
||
if (! (**cptr & 0x80))
|
||
tmxr_linemsg (t, "\b \b");
|
||
}
|
||
break;
|
||
case 033:
|
||
/* Escape [ X. */
|
||
if (tmxr_getc_ln (t) != '[' + TMXR_VALID)
|
||
break;
|
||
switch (tmxr_getc_ln (t) - TMXR_VALID) {
|
||
case 'A': /* Up arrow */
|
||
if (*cptr <= cbuf) {
|
||
*cptr = cbuf + strlen (cbuf);
|
||
if (*cptr > cbuf)
|
||
tmxr_linemsg (t, cbuf);
|
||
}
|
||
break;
|
||
case 'B': /* Down arrow */
|
||
goto erase_line;
|
||
}
|
||
break;
|
||
default:
|
||
if (c < ' ' || *cptr > cbuf+CBUFSIZE-5)
|
||
break;
|
||
*(*cptr)++ = c;
|
||
tmxr_putc_ln (t, c);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Getting a char from a terminal with the given number.
|
||
* Returns -1 if there is no char to input.
|
||
*/
|
||
int vt_getc (int num)
|
||
{
|
||
TMLN *t = &tty_line [num];
|
||
extern int32 sim_int_char;
|
||
int c;
|
||
time_t now;
|
||
|
||
if (! t->conn) {
|
||
/* Пользователь отключился. */
|
||
if (t->ipad) {
|
||
besm6_debug ("*** tty%d: disconnecting %s",
|
||
num,
|
||
t->ipad);
|
||
t->ipad = NULL;
|
||
}
|
||
tty_setmode (tty_unit+num, TTY_OFFLINE_STATE, 0, 0);
|
||
tty_unit[num].flags &= ~TTY_STATE_MASK;
|
||
return -1;
|
||
}
|
||
if (t->rcve) {
|
||
/* A telnet line. */
|
||
c = tmxr_getc_ln (t);
|
||
if (! (c & TMXR_VALID)) {
|
||
now = time (0);
|
||
if (now > tty_last_time[num] + 5*60) {
|
||
++tty_idle_count[num];
|
||
if (tty_idle_count[num] > 3) {
|
||
tmxr_linemsg (t, "\r\nSIMH: END OF SESSION\r\n");
|
||
tmxr_reset_ln (t);
|
||
return -1;
|
||
}
|
||
tmxr_linemsg (t, "\r\nSIMH: WAKE UP!\r\n");
|
||
tty_last_time[num] = now;
|
||
}
|
||
return -1;
|
||
}
|
||
tty_idle_count[num] = 0;
|
||
tty_last_time[num] = time (0);
|
||
|
||
if (tty_unit[num].flags & TTY_CMDLINE_MASK) {
|
||
/* Continuing CLI mode. */
|
||
vt_cmd_loop (num, c & 0377);
|
||
return -1;
|
||
}
|
||
if ((c & 0377) == sim_int_char) {
|
||
/* Entering CLI mode. */
|
||
tty_unit[num].flags |= TTY_CMDLINE_MASK;
|
||
tmxr_linemsg (t, "sim>");
|
||
vt_cptr[num] = vt_cbuf[num];
|
||
return -1;
|
||
}
|
||
} else {
|
||
/* Console (keyboard) input. */
|
||
c = sim_poll_kbd();
|
||
if (c == SCPE_STOP) {
|
||
stop_cpu = 1; /* just in case */
|
||
}
|
||
if (! (c & SCPE_KFLAG))
|
||
return -1;
|
||
}
|
||
return c & 0377;
|
||
}
|
||
|
||
/*
|
||
* Reading UTF-8, returning KOI-7.
|
||
* The resulting char is in the range 0..0177.
|
||
* If no input, returns -1.
|
||
*/
|
||
static int vt_kbd_input_unicode (int num)
|
||
{
|
||
int c1, c2, c3, r;
|
||
again:
|
||
r = vt_getc (num);
|
||
if (r < 0 || r > 0377)
|
||
return r;
|
||
c1 = r & 0377;
|
||
if (! (c1 & 0x80))
|
||
return unicode_to_koi7 (c1);
|
||
|
||
r = vt_getc (num);
|
||
if (r < 0 || r > 0377)
|
||
return r;
|
||
c2 = r & 0377;
|
||
if (! (c1 & 0x20))
|
||
return unicode_to_koi7 ((c1 & 0x1f) << 6 | (c2 & 0x3f));
|
||
|
||
r = vt_getc (num);
|
||
if (r < 0 || r > 0377)
|
||
return r;
|
||
c3 = r & 0377;
|
||
if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF) {
|
||
/* Skip zero width no-break space. */
|
||
goto again;
|
||
}
|
||
return unicode_to_koi7 ((c1 & 0x0f) << 12 | (c2 & 0x3f) << 6 |
|
||
(c3 & 0x3f));
|
||
}
|
||
|
||
/*
|
||
* Alternatively, entering Cyrillics can be done without switching keyboard layouts.
|
||
* Period and comma are entered with shift, less-than and greater-than are mapped to tilde-grave.
|
||
* Semicolon is }, quote is |.
|
||
*/
|
||
static int vt_kbd_input_koi7 (int num)
|
||
{
|
||
int r;
|
||
|
||
r = vt_getc (num);
|
||
if (r < 0 || r > 0377)
|
||
return r;
|
||
r &= 0377;
|
||
switch (r) {
|
||
case '\r': return '\003';
|
||
case 'q': return 'j';
|
||
case 'w': return 'c';
|
||
case 'e': return 'u';
|
||
case 'r': return 'k';
|
||
case 't': return 'e';
|
||
case 'y': return 'n';
|
||
case 'u': return 'g';
|
||
case 'i': return '{';
|
||
case 'o': return '}';
|
||
case 'p': return 'z';
|
||
case '[': return 'h';
|
||
case '{': return '[';
|
||
case 'a': return 'f';
|
||
case 's': return 'y';
|
||
case 'd': return 'w';
|
||
case 'f': return 'a';
|
||
case 'g': return 'p';
|
||
case 'h': return 'r';
|
||
case 'j': return 'o';
|
||
case 'k': return 'l';
|
||
case 'l': return 'd';
|
||
case ';': return 'v';
|
||
case '}': return ';';
|
||
case '\'': return '|';
|
||
case '|': return '\'';
|
||
case 'z': return 'q';
|
||
case 'x': return '~';
|
||
case 'c': return 's';
|
||
case 'v': return 'm';
|
||
case 'b': return 'i';
|
||
case 'n': return 't';
|
||
case 'm': return 'x';
|
||
case ',': return 'b';
|
||
case '<': return ',';
|
||
case '.': return '`';
|
||
case '>': return '.';
|
||
case '~': return '>';
|
||
case '`': return '<';
|
||
default: return r;
|
||
}
|
||
}
|
||
|
||
int odd_parity(unsigned char c)
|
||
{
|
||
c = (c & 0x55) + ((c >> 1) & 0x55);
|
||
c = (c & 0x33) + ((c >> 2) & 0x33);
|
||
c = (c & 0x0F) + ((c >> 4) & 0x0F);
|
||
return c & 1;
|
||
}
|
||
|
||
/*
|
||
* Handling input from all connected terminals.
|
||
*/
|
||
void vt_receive()
|
||
{
|
||
uint32 workset = vt_mask;
|
||
int num;
|
||
|
||
TTY_IN = 0;
|
||
for (num = besm6_highest_bit (workset) - TTY_MAX;
|
||
workset; num = besm6_highest_bit (workset) - TTY_MAX) {
|
||
uint32 mask = 1 << (TTY_MAX - num);
|
||
switch (tty_instate[num]) {
|
||
case 0:
|
||
switch (tty_unit[num].flags & TTY_CHARSET_MASK) {
|
||
case TTY_KOI7_JCUKEN_CHARSET:
|
||
tty_typed[num] = vt_kbd_input_koi7 (num);
|
||
break;
|
||
case TTY_KOI7_QWERTY_CHARSET:
|
||
tty_typed[num] = vt_getc (num);
|
||
break;
|
||
case TTY_UNICODE_CHARSET:
|
||
tty_typed[num] = vt_kbd_input_unicode (num);
|
||
break;
|
||
default:
|
||
tty_typed[num] = '?';
|
||
break;
|
||
}
|
||
if (tty_typed[num] < 0) {
|
||
break;
|
||
}
|
||
if (tty_typed[num] <= 0177) {
|
||
if (tty_typed[num] == '\r' || tty_typed[num] == '\n')
|
||
tty_typed[num] = 3; /* ETX is used as Enter */
|
||
if (tty_typed[num] == '\177')
|
||
tty_typed[num] = '\b'; /* ASCII DEL -> BS */
|
||
tty_instate[num] = 1;
|
||
TTY_IN |= mask; /* start bit */
|
||
GRP |= GRP_TTY_START; /* not used ? */
|
||
/* auto-enabling the interrupt just in case
|
||
* (seems to be unneeded as the interrupt is never disabled)
|
||
*/
|
||
MGRP |= GRP_SERIAL;
|
||
vt_receiving |= mask;
|
||
}
|
||
break;
|
||
case 1: case 2: case 3: case 4: case 5: case 6: case 7:
|
||
/* need inverted byte */
|
||
TTY_IN |= (tty_typed[num] & (1 << (tty_instate[num]-1))) ? 0 : mask;
|
||
tty_instate[num]++;
|
||
break;
|
||
case 8:
|
||
TTY_IN |= odd_parity(tty_typed[num]) ? 0 : mask; /* even parity of inverted */
|
||
tty_instate[num]++;
|
||
break;
|
||
case 9: case 10: case 11:
|
||
/* stop bits are 0 */
|
||
tty_instate[num]++;
|
||
break;
|
||
case 12:
|
||
tty_instate[num] = 0; /* ready for the next char */
|
||
vt_receiving &= ~mask;
|
||
break;
|
||
}
|
||
workset &= ~mask;
|
||
}
|
||
if (vt_receiving)
|
||
vt_idle = 0;
|
||
}
|
||
|
||
/*
|
||
* Checking if all terminals are idle.
|
||
* SIMH should not enter idle mode until they are.
|
||
*/
|
||
int vt_is_idle ()
|
||
{
|
||
return (tt_mask ? vt_idle > 300 : vt_idle > 10);
|
||
}
|
||
|
||
int tty_query ()
|
||
{
|
||
/* besm6_debug ("*** TTY: query");*/
|
||
return TTY_IN;
|
||
}
|
||
|
||
void consul_print (int dev_num, uint32 cmd)
|
||
{
|
||
int line_num = dev_num + TTY_MAX + 1;
|
||
if (tty_dev.dctrl)
|
||
besm6_debug(">>> CONSUL%o: %03o", line_num, cmd & 0377);
|
||
cmd &= 0177;
|
||
switch (tty_unit[line_num].flags & TTY_STATE_MASK) {
|
||
case TTY_VT340_STATE:
|
||
vt_send (line_num, cmd,
|
||
(tty_unit[line_num].flags & TTY_BSPACE_MASK) == TTY_DESTRUCTIVE_BSPACE);
|
||
break;
|
||
case TTY_CONSUL_STATE:
|
||
besm6_debug(">>> CONSUL%o: Native charset not implemented", line_num);
|
||
break;
|
||
}
|
||
PRP |= CONS_CAN_PRINT[dev_num];
|
||
vt_idle = 0;
|
||
}
|
||
|
||
void consul_receive ()
|
||
{
|
||
int c, line_num, dev_num;
|
||
|
||
for (dev_num = 0; dev_num < 2; ++dev_num){
|
||
line_num = dev_num + TTY_MAX + 1;
|
||
if (! tty_line[line_num].conn)
|
||
continue;
|
||
switch (tty_unit[line_num].flags & TTY_CHARSET_MASK) {
|
||
case TTY_KOI7_JCUKEN_CHARSET:
|
||
c = vt_kbd_input_koi7 (line_num);
|
||
break;
|
||
case TTY_KOI7_QWERTY_CHARSET:
|
||
c = vt_getc (line_num);
|
||
break;
|
||
case TTY_UNICODE_CHARSET:
|
||
c = vt_kbd_input_unicode (line_num);
|
||
break;
|
||
default:
|
||
c = '?';
|
||
break;
|
||
}
|
||
if (c >= 0 && c <= 0177) {
|
||
CONSUL_IN[dev_num] = odd_parity(c) ? c | 0200 : c;
|
||
if (c == '\r' || c == '\n')
|
||
CONSUL_IN[dev_num] = 3;
|
||
PRP |= CONS_HAS_INPUT[dev_num];
|
||
vt_idle = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
uint32 consul_read (int num)
|
||
{
|
||
if (tty_dev.dctrl)
|
||
besm6_debug("<<< CONSUL%o: %03o", num+TTY_MAX+1, CONSUL_IN[num]);
|
||
return CONSUL_IN[num];
|
||
}
|