| /* |
| * besm6_tty.c: BESM-6 teletype device |
| * |
| * Copyright (c) 2009, Leo Broukhis |
| * Copyright (c) 2009, Serge Vakulenko |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. |
| * |
| * You can redistribute this program and/or modify it under the terms of |
| * the GNU General Public License as published by the Free Software Foundation; |
| * either version 2 of the License, or (at your discretion) any later version. |
| * See the accompanying file "COPYING" for more details. |
| */ |
| |
| #include "besm6_defs.h" |
| #include "sim_sock.h" |
| #include "sim_tmxr.h" |
| #include <time.h> |
| |
| #define TTY_MAX 24 /* Количество последовательных терминалов */ |
| #define LINES_MAX TTY_MAX + 2 /* Включая параллельные интерфейсы "Консулов" */ |
| /* |
| * Согласно таблице в 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) |
| { |
| /* Требуется инверсия */ |
| sym ^= 31; |
| switch (sym) { |
| case 0: |
| reg = rus; |
| break; |
| case 27: |
| reg = dig; |
| break; |
| case 31: |
| reg = lat; |
| break; |
| default: |
| return reg[sym]; |
| } |
| return ""; |
| } |
| |
| /* Только для последовательных линий */ |
| 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]; |
| |
| 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 }; |
| |
| /* Буфера командных строк для режима telnet. */ |
| 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 char *get_sim_sw (char *cptr); |
| |
| UNIT tty_unit [] = { |
| { UDATA (vt_clk, UNIT_DIS, 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 } |
| }; |
| |
| /* |
| * Дескрипторы линий для мультиплексора TMXR. |
| * Поле .conn содержит номер сокета и означает занятую линию. |
| * Для локальных терминалов делаем .conn = 1. |
| * Чтобы нумерация линий совпадала с нумерацией терминалов |
| * (с единицы), нулевую линию держим занятой (.conn = 1). |
| * Поле .rcve устанавливается в 1 для сетевых соединений. |
| * Для локальных терминалов оно равно 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 */ |
| /* Готовность устройства в READY2 инверсная, а устройство всегда готово */ |
| /* Провоцируем передачу */ |
| PRP |= CONS_CAN_PRINT[0] | CONS_CAN_PRINT[1]; |
| return sim_activate (tty_unit, 1000*MSEC/300); |
| } |
| |
| /* 19 р ГРП, 300 Гц */ |
| t_stat vt_clk (UNIT * this) |
| { |
| /* Телетайпы работают на 10 бод */ |
| static int clk_divider = 1<<29; |
| GRP |= MGRP & BBIT(19); |
| |
| /* Опрашиваем сокеты на приём. */ |
| tmxr_poll_rx (&tty_desc); |
| |
| vt_print(); |
| vt_receive(); |
| consul_receive(); |
| |
| if (! (clk_divider >>= 1)) { |
| tt_print(); |
| /* Прием не реализован */ |
| clk_divider = 1<<29; |
| } |
| |
| /* Есть новые сетевые подключения? */ |
| int num = tmxr_poll_conn (&tty_desc); |
| if (num > 0 && num <= LINES_MAX) { |
| char buf [80]; |
| TMLN *t = &tty_line [num]; |
| besm6_debug ("*** tty%d: новое подключение от %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); |
| |
| /* Ввод ^C, чтобы получить приглашение. */ |
| t->rxb [t->rxbpi++] = '\3'; |
| } |
| |
| /* Опрашиваем сокеты на передачу. */ |
| tmxr_poll_tx (&tty_desc); |
| |
| return sim_activate (this, 1000*MSEC/300); |
| } |
| |
| t_stat tty_setmode (UNIT *u, int32 val, 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; |
| } |
| |
| /* |
| * Разрешение подключения к терминалам через telnet. |
| * Делается командой: |
| * attach tty <порт> |
| * Здесь <порт> - номер порта telnet, например 4199. |
| */ |
| t_stat tty_attach (UNIT *u, char *cptr) |
| { |
| int num = u - tty_unit; |
| int r, m, n; |
| |
| if (*cptr >= '0' && *cptr <= '9') { |
| /* Сохраняем и восстанавливаем все .conn, |
| * так как tmxr_attach() их обнуляет. */ |
| for (m=0, n=1; n<=LINES_MAX; ++n) |
| if (tty_line[n].conn) |
| m |= 1 << (LINES_MAX-n); |
| /* Неважно, какой номер порта указывать в команде задания |
| * порта telnet. Можно tty, можно tty1 - без разницы. */ |
| 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; |
| } |
| if (strcmp (cptr, "/dev/tty") == 0) { |
| /* Консоль. */ |
| 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 ("*** консоль на T%03o", num); |
| return 0; |
| } |
| if (strcmp (cptr, "/dev/null") == 0) { |
| /* Запрещаем терминал. */ |
| 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 ("*** отключение терминала T%03o", num); |
| return 0; |
| } |
| return SCPE_ALATT; |
| } |
| |
| t_stat tty_detach (UNIT *u) |
| { |
| return tmxr_detach (&tty_desc, &tty_unit[0]); |
| } |
| |
| /* |
| * Управление терминалами. |
| * set ttyN unicode - выбор кодировки UTF-8 |
| * set ttyN jcuken - выбор кодировки КОИ-7, раскладка йцукен |
| * set ttyN qwerty - выбор кодировки КОИ-7, раскладка яверты |
| * set ttyN off - отключение |
| * set ttyN tt - установка типа терминала "Телетайп" |
| * set ttyN vt - установка типа терминала "Видеотон-340" |
| * set ttyN consul - установка типа терминала "Consul-254" |
| * set ttyN destrbs - "стирающий" backspace |
| * set ttyN authbs - классический backspace |
| * set tty disconnect=N - принудительное завершение сеанса telnet |
| * show tty - просмотр режимов терминалов |
| * show tty connections - просмотр IP-адресов и времени соединений |
| * show tty statistics - просмотр счетчиков переданных и принятых байтов |
| */ |
| 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, 1, NULL, |
| "DISCONNECT", &tmxr_dscln, NULL, (void*) &tty_desc }, |
| { 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 ("*** телетайпы: передача %08o", mask); */ |
| |
| TTY_OUT = mask; |
| } |
| |
| /* |
| * Выдача символа на терминал с указанным номером. |
| */ |
| void vt_putc (int num, int c) |
| { |
| TMLN *t = &tty_line [num]; |
| |
| if (! t->conn) |
| return; |
| if (t->rcve) { |
| /* Передача через telnet. */ |
| tmxr_putc_ln (t, c); |
| } else { |
| /* Вывод на консоль. */ |
| if (t->txlog) { /* log if available */ |
| fputc (c, t->txlog); |
| if (c == '\n') |
| fflush (t->txlog); |
| } |
| fputc (c, stdout); |
| fflush (stdout); |
| } |
| } |
| |
| /* |
| * Выдача строки на терминал с указанным номером. |
| */ |
| void vt_puts (int num, const char *s) |
| { |
| TMLN *t = &tty_line [num]; |
| |
| if (! t->conn) |
| return; |
| if (t->rcve) { |
| /* Передача через telnet. */ |
| tmxr_linemsg (t, (char*) s); |
| } else { |
| /* Вывод на консоль. */ |
| if (t->txlog) /* log if available */ |
| fputs (s, t->txlog); |
| fputs (s, stdout); |
| fflush (stdout); |
| } |
| } |
| |
| const char * koi7_rus_to_unicode [32] = { |
| "Ю", "А", "Б", "Ц", "Д", "Е", "Ф", "Г", |
| "Х", "И", "Й", "К", "Л", "М", "Н", "О", |
| "П", "Я", "Р", "С", "Т", "У", "Ж", "В", |
| "Ь", "Ы", "З", "Ш", "Э", "Щ", "Ч", "\0x7f", |
| }; |
| |
| 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) { |
| /* Стираем предыдущий символ. */ |
| vt_puts (num, "D \033["); |
| } |
| sym = 'D'; |
| break; |
| case '\v': |
| case '\033': |
| case '\0': |
| /* Выдаём управляющий символ. */ |
| break; |
| case '\037': |
| /* Очистка экрана */ |
| vt_puts (num, "\033[H\033["); |
| sym = 'J'; |
| break; |
| case '\n': |
| /* На VDT-340 также возвращал курсор в 1-ю позицию */ |
| vt_putc (num, '\r'); |
| sym = '\n'; |
| break; |
| case '\f': |
| /* Сообщение ERR при нажатии некоторых управляющих |
| * клавиш выдается с использованием reverse wraparound. |
| */ |
| vt_puts(num, "\033["); |
| sym = 'H'; |
| break; |
| case '\r': |
| case '\003': |
| /* Неотображаемые символы */ |
| sym = 0; |
| break; |
| default: |
| if (sym < ' ') { |
| /* Нефункциональные ctrl-символы были видны в половинной яркости */ |
| vt_puts (num, "\033[2m"); |
| vt_putc (num, sym | 0x40); |
| vt_puts (num, "\033["); |
| /* Завершаем ESC-последовательность */ |
| sym = 'm'; |
| } |
| } |
| if (sym) |
| vt_putc (num, sym); |
| } else |
| vt_puts (num, koi7_rus_to_unicode[sym - 0x60]); |
| } |
| |
| /* |
| * Обработка выдачи на все подключенные терминалы. |
| */ |
| 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; |
| } |
| |
| /* Ввод с телетайпа не реализован; вывод работает только при использовании |
| * модельного времени. |
| */ |
| 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; |
| } |
| |
| /* |
| * Перекодировка из Unicode в КОИ-7. |
| * Если нет соответствия, возвращает -1. |
| */ |
| static int unicode_to_koi7 (unsigned val) |
| { |
| switch (val) { |
| case '\0'... '_': return val; |
| case 'a' ... 'z': return val + 'Z' - 'z'; |
| 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, char *cptr) |
| { |
| char gbuf [CBUFSIZE]; |
| int len; |
| |
| cptr = 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, char *cptr) |
| { |
| TMLN *t = &tty_line [num]; |
| char gbuf [CBUFSIZE]; |
| MTAB *m; |
| int len; |
| |
| cptr = 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, char *cptr) |
| { |
| return SCPE_EXIT; |
| } |
| |
| static t_stat cmd_help (int32 num, 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, char *cptr) |
| { |
| TMLN *t = &tty_line [num]; |
| char gbuf [CBUFSIZE]; |
| CTAB *c; |
| |
| cptr = get_sim_sw (cptr); |
| if (! cptr) |
| return SCPE_INVSW; |
| if (! *cptr) { |
| /* Список всех команд. */ |
| 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; |
| /* Описание конкретной команды. */ |
| tmxr_linemsg (t, c->help); |
| return SCPE_OK; |
| } |
| |
| /* |
| * Выполнение командной строки. |
| */ |
| void vt_cmd_exec (int num) |
| { |
| TMLN *t = &tty_line [num]; |
| char *cptr, gbuf [CBUFSIZE]; |
| 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); |
| } |
| } |
| |
| /* |
| * Режим управляющей командной строки. |
| */ |
| 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) { |
| /* Пустая строка - возврат в обычный режим. */ |
| tty_unit[num].flags &= ~TTY_CMDLINE_MASK; |
| break; |
| } |
| /* Выполнение. */ |
| **cptr = 0; |
| vt_cmd_exec (num); |
| tmxr_linemsg (t, "sim>"); |
| *cptr = vt_cbuf[num]; |
| break; |
| case '\b': |
| case 0177: |
| /* Стирание предыдущего символа. */ |
| if (*cptr <= cbuf) |
| break; |
| tmxr_linemsg (t, "\b \b"); |
| while (*cptr > cbuf) { |
| --*cptr; |
| if (! (**cptr & 0x80)) |
| break; |
| } |
| break; |
| case 'U' & 037: |
| /* Стирание всей строки. */ |
| 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': /* стрелка вверх */ |
| if (*cptr <= cbuf) { |
| *cptr = cbuf + strlen (cbuf); |
| if (*cptr > cbuf) |
| tmxr_linemsg (t, cbuf); |
| } |
| break; |
| case 'B': /* стрелка вниз */ |
| goto erase_line; |
| } |
| break; |
| default: |
| if (c < ' ' || *cptr > cbuf+CBUFSIZE-5) |
| break; |
| *(*cptr)++ = c; |
| tmxr_putc_ln (t, c); |
| break; |
| } |
| } |
| |
| /* |
| * Ввод символа с терминала с указанным номером. |
| * Если нет приёма, возвращает -1. |
| * В случае прерывания возвращает 0400 (только для консоли). |
| */ |
| 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: отключение %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) { |
| /* Приём через telnet. */ |
| 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\nКОНЕЦ СЕАНСА\r\n"); |
| tmxr_reset_ln (t); |
| return -1; |
| } |
| tmxr_linemsg (t, "\r\nНЕ СПАТЬ!\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) { |
| /* Продолжение режима управляющей командной строки. */ |
| vt_cmd_loop (num, c & 0377); |
| return -1; |
| } |
| if ((c & 0377) == sim_int_char) { |
| /* Вход в режим управляющей командной строки. */ |
| tty_unit[num].flags |= TTY_CMDLINE_MASK; |
| tmxr_linemsg (t, "sim>"); |
| vt_cptr[num] = vt_cbuf[num]; |
| return -1; |
| } |
| } else { |
| /* Ввод с клавиатуры. */ |
| c = sim_poll_kbd(); |
| if (c == SCPE_STOP) |
| return 0400; /* прерывание */ |
| if (! (c & SCPE_KFLAG)) |
| return -1; |
| } |
| return c & 0377; |
| } |
| |
| /* |
| * Ввод символа с клавиатуры. |
| * Перекодировка из UTF-8 в КОИ-7. |
| * Полученный символ находится в диапазоне 0..0177. |
| * Если нет ввода, возвращает -1. |
| * В случае прерывания (^E) возвращает 0400. |
| */ |
| 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)); |
| } |
| |
| /* |
| * Альтернативный вариант ввода, не требующий переключения на русскую клавиатуру. |
| * Символы "точка" и "запятая" вводятся через shift, "больше-меньше" - "тильда-гравис". |
| * "точка с запятой" - "закр. фиг. скобка", "апостроф" - "верт. черта". |
| */ |
| 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; |
| } |
| |
| /* |
| * Обработка ввода со всех подключенных терминалов. |
| */ |
| 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) { |
| /* TODO: обработать исключение от "неоператорского" терминала */ |
| sim_interval = 0; |
| break; |
| } |
| if (tty_typed[num] <= 0177) { |
| if (tty_typed[num] == '\r' || tty_typed[num] == '\n') |
| tty_typed[num] = 3; /* ^C - конец строки */ |
| 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; /* не используется ? */ |
| MGRP |= BBIT(19); /* для терминалов по методу МГУ */ |
| vt_receiving |= mask; |
| } |
| break; |
| case 1 ... 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 ... 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; |
| } |
| |
| /* |
| * Выясняем, остановлены ли терминалы. Нужно для входа в "спящий" режим. |
| */ |
| int vt_is_idle () |
| { |
| return (tt_mask ? vt_idle > 300 : vt_idle > 10); |
| } |
| |
| int tty_query () |
| { |
| /* besm6_debug ("*** телетайпы: приём");*/ |
| 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]; |
| } |