// (C) 2024 by Folkert van Heusden // Released under MIT license #include "gen.h" #if defined(ESP32) #include <Arduino.h> #endif #if defined(ESP32) #include <lwip/sockets.h> #include <arpa/inet.h> #include <sys/socket.h> #include <driver/uart.h> #elif defined(_WIN32) #include <ws2tcpip.h> #include <winsock2.h> #else #include <poll.h> #include <arpa/inet.h> #include <sys/socket.h> #endif #if IS_POSIX #include <errno.h> #include <fcntl.h> #include <termios.h> #include <thread> #endif #include <cstring> #include <unistd.h> #include "bus.h" #include "cpu.h" #include "dc11.h" #include "log.h" #include "utils.h" #define ESP32_UART UART_NUM_1 // this line is reserved for a serial port constexpr const int serial_line = 3; const char *const dc11_register_names[] { "RCSR", "RBUF", "TSCR", "TBUF" }; bool setup_telnet_session(const int fd) { uint8_t dont_auth[] = { 0xff, 0xf4, 0x25 }; uint8_t suppress_goahead[] = { 0xff, 0xfb, 0x03 }; uint8_t dont_linemode[] = { 0xff, 0xfe, 0x22 }; uint8_t dont_new_env[] = { 0xff, 0xfe, 0x27 }; uint8_t will_echo[] = { 0xff, 0xfb, 0x01 }; uint8_t dont_echo[] = { 0xff, 0xfe, 0x01 }; uint8_t noecho[] = { 0xff, 0xfd, 0x2d }; // uint8_t charset[] = { 0xff, 0xfb, 0x01 }; if (write(fd, dont_auth, sizeof dont_auth) != sizeof dont_auth) return false; if (write(fd, suppress_goahead, sizeof suppress_goahead) != sizeof suppress_goahead) return false; if (write(fd, dont_linemode, sizeof dont_linemode) != sizeof dont_linemode) return false; if (write(fd, dont_new_env, sizeof dont_new_env) != sizeof dont_new_env) return false; if (write(fd, will_echo, sizeof will_echo) != sizeof will_echo) return false; if (write(fd, dont_echo, sizeof dont_echo) != sizeof dont_echo) return false; if (write(fd, noecho, sizeof noecho) != sizeof noecho) return false; return true; } dc11::dc11(const int base_port, bus *const b): base_port(base_port), b(b) { #if defined(_WIN32) pfds = new WSAPOLLFD[dc11_n_lines * 2](); #else pfds = new pollfd[dc11_n_lines * 2](); #endif // TODO move to begin() th = new std::thread(std::ref(*this)); } dc11::~dc11() { DOLOG(debug, false, "DC11 closing"); stop_flag = true; if (th) { th->join(); delete th; } delete [] pfds; #if defined(ESP32) // won't work due to freertos thread #elif IS_POSIX close(serial_fd); if (serial_th) { serial_th->join(); delete serial_th; } #endif } void dc11::show_state(console *const cnsl) const { for(int i=0; i<4; i++) { cnsl->put_string_lf(format("* LINE %d", i + 1)); if (i == serial_line) { cnsl->put_string_lf(format(" Serial thread running: %s", serial_thread_running ? "true": "false" )); cnsl->put_string_lf(format(" Serial enabled: %s", serial_enabled ? "true": "false" )); } else { if (pfds[dc11_n_lines + i].fd != INVALID_SOCKET) cnsl->put_string_lf(" Connected to: " + get_endpoint_name(pfds[dc11_n_lines + i].fd)); } std::unique_lock<std::mutex> lck(input_lock[i]); cnsl->put_string_lf(format(" Characters in buffer: %zu", recv_buffers[i].size())); cnsl->put_string_lf(format(" RX interrupt enabled: %s", is_rx_interrupt_enabled(i) ? "true": "false" )); cnsl->put_string_lf(format(" TX interrupt enabled: %s", is_tx_interrupt_enabled(i) ? "true": "false" )); } } void dc11::test_serial(const std::string & txt) const { for(int i=0; i<dc11_n_lines; i++) { if (i == serial_line) { DOLOG(info, false, "DC11 serial test line %d", i); #if defined(ESP32) uart_write_bytes(ESP32_UART, txt.c_str(), txt.size()); #elif IS_POSIX if (write(serial_fd, txt.c_str(), txt.size()) != ssize_t(txt.size())) DOLOG(warning, false, "DC11 failed to send test string to line %d", i); #endif } else if (pfds[dc11_n_lines + i].fd != INVALID_SOCKET) { DOLOG(info, false, "DC11 socket line %d", i); if (write(pfds[dc11_n_lines + i].fd, txt.c_str(), txt.size()) != ssize_t(txt.size())) DOLOG(warning, false, "DC11 failed to send test string to line %d", i); } else { DOLOG(info, false, "DC11 line %d not connected", i); } } } void dc11::trigger_interrupt(const int line_nr, const bool is_tx) { TRACE("DC11: interrupt for line %d, %s", line_nr, is_tx ? "TX" : "RX"); b->getCpu()->queue_interrupt(5, 0300 + line_nr * 010 + 4 * is_tx); } void dc11::operator()() { set_thread_name("kek:DC11"); DOLOG(info, true, "DC11 thread started"); for(int i=0; i<dc11_n_lines; i++) { // client session pfds[dc11_n_lines + i].fd = INVALID_SOCKET; if (i == serial_line) { // prevent accept() on this socket pfds[i].fd = INVALID_SOCKET; continue; } pfds[dc11_n_lines + i].events = POLLIN; // listen on port int port = base_port + i + 1; pfds[i].fd = socket(AF_INET, SOCK_STREAM, 0); int reuse_addr = 1; if (setsockopt(pfds[i].fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse_addr, sizeof(reuse_addr)) == -1) { close(pfds[i].fd); pfds[i].fd = INVALID_SOCKET; DOLOG(warning, true, "Cannot set reuseaddress for port %d (DC11)", port); continue; } set_nodelay(pfds[i].fd); sockaddr_in listen_addr; memset(&listen_addr, 0, sizeof(listen_addr)); listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); listen_addr.sin_port = htons(port); if (bind(pfds[i].fd, reinterpret_cast<struct sockaddr *>(&listen_addr), sizeof(listen_addr)) == -1) { close(pfds[i].fd); pfds[i].fd = INVALID_SOCKET; DOLOG(warning, true, "Cannot bind to port %d (DC11)", port); continue; } if (listen(pfds[i].fd, SOMAXCONN) == -1) { close(pfds[i].fd); pfds[i].fd = INVALID_SOCKET; DOLOG(warning, true, "Cannot listen on port %d (DC11)", port); continue; } pfds[i].events = POLLIN; } while(!stop_flag) { #if defined(_WIN32) int rc = WSAPoll(pfds, dc11_n_lines * 2, 100); #else int rc = poll(pfds, dc11_n_lines * 2, 100); #endif if (rc == 0) continue; // accept any new session for(int i=0; i<dc11_n_lines; i++) { if (pfds[i].revents != POLLIN) continue; int client_i = dc11_n_lines + i; // disconnect any existing client session // yes, one can ddos with this if (pfds[client_i].fd != INVALID_SOCKET) { close(pfds[client_i].fd); DOLOG(info, false, "Restarting session for port %d", base_port + i + 1); } pfds[client_i].fd = accept(pfds[i].fd, nullptr, nullptr); if (setup_telnet_session(pfds[client_i].fd) == false) { close(pfds[client_i].fd); pfds[client_i].fd = INVALID_SOCKET; } if (pfds[client_i].fd != INVALID_SOCKET) { set_nodelay(pfds[client_i].fd); std::unique_lock<std::mutex> lck(input_lock[i]); registers[i * 4 + 0] |= 0160000; // "ERROR", RING INDICATOR, CARRIER TRANSITION if (is_rx_interrupt_enabled(i)) trigger_interrupt(i, false); } } // receive data for(int i=dc11_n_lines; i<dc11_n_lines * 2; i++) { if (pfds[i].revents != POLLIN) continue; char buffer[32] { }; int rc_read = read(pfds[i].fd, buffer, sizeof buffer); int line_nr = i - dc11_n_lines; std::unique_lock<std::mutex> lck(input_lock[line_nr]); if (rc_read <= 0) { // closed or error? DOLOG(info, false, "Failed reading from port %d", i - dc11_n_lines + 1); registers[line_nr * 4 + 0] |= 0140000; // "ERROR", CARRIER TRANSITION close(pfds[i].fd); pfds[i].fd = INVALID_SOCKET; } else { for(int k=0; k<rc_read; k++) recv_buffers[line_nr].push_back(buffer[k]); registers[line_nr * 4 + 0] |= 128; // DONE: bit 7 } if (is_rx_interrupt_enabled(line_nr)) trigger_interrupt(line_nr, false); } } DOLOG(info, true, "DC11 thread terminating"); for(int i=0; i<dc11_n_lines * 2; i++) { if (pfds[i].fd != INVALID_SOCKET) close(pfds[i].fd); } } #if defined(ESP32) void dc11_thread_wrapper_serial_handler(void *const c) { dc11 *const d = reinterpret_cast<dc11 *>(c); d->serial_handler(); vTaskSuspend(nullptr); } void dc11::set_serial(const int bitrate, const int rx, const int tx) { if (serial_thread_running) { DOLOG(info, true, "DC11: serial port already configured"); return; } Serial.printf("Tick period: %d\r\n", portTICK_PERIOD_MS); serial_thread_running = true; // Configure UART parameters static uart_config_t uart_config = { .baud_rate = bitrate, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, .rx_flow_ctrl_thresh = 122, }; ESP_ERROR_CHECK(uart_param_config(ESP32_UART, &uart_config)); ESP_ERROR_CHECK(uart_set_pin(ESP32_UART, tx, rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); // Setup UART buffered IO with event queue const int uart_buffer_size = 1024 * 2; static QueueHandle_t uart_queue; // Install UART driver using an event queue here ESP_ERROR_CHECK(uart_driver_install(ESP32_UART, uart_buffer_size, uart_buffer_size, 10, &uart_queue, 0)); const char msg[] = "Press enter to connect\r\n"; uart_write_bytes(ESP32_UART, msg, sizeof(msg) - 1); xTaskCreate(&dc11_thread_wrapper_serial_handler, "dc11_tty", 3072, this, 1, nullptr); } #elif IS_POSIX void dc11::set_serial(const int bitrate, const std::string & device) { serial_fd = open(device.c_str(), O_RDWR); if (serial_fd == -1) { DOLOG(warning, false, "DC11 failed to access %s: %s", device.c_str(), strerror(errno)); return; // TODO error handling } serial_thread_running = true; // from https://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c termios tty { }; if (tcgetattr(serial_fd, &tty) == -1) { DOLOG(warning, false, "DC11 tcgetattr failed: %s", strerror(errno)); close(serial_fd); return; } cfsetospeed(&tty, bitrate); cfsetispeed(&tty, bitrate); tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars // disable IGNBRK for mismatched speed tests; otherwise receive break // as \000 chars tty.c_iflag &= ~IGNBRK; // disable break processing tty.c_lflag = 0; // no signaling chars, no echo, // no canonical processing tty.c_oflag = 0; // no remapping, no delays tty.c_cc[VMIN] = 0; // read doesn't block tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls, // enable reading tty.c_cflag &= ~(PARENB | PARODD); // shut off parity tty.c_cflag &= ~CSTOPB; tty.c_cflag &= ~CRTSCTS; if (tcsetattr(serial_fd, TCSANOW, &tty) == -1) { DOLOG(warning, false, "DC11 tcsetattr failed: %s", strerror(errno)); close(serial_fd); return; } serial_th = new std::thread(&dc11::serial_handler, this); } #endif void dc11::serial_handler() { set_thread_name("kek:dc11-serial"); TRACE("DC11: serial handler thread started"); #if IS_POSIX pollfd fds[] = { { serial_fd, POLLIN, 0 } }; #endif while(!stop_flag) { char c = 0; #if defined(ESP32) yield(); size_t n_available = 0; ESP_ERROR_CHECK(uart_get_buffered_data_len(ESP32_UART, &n_available)); if (n_available == 0) { vTaskDelay(4 / portTICK_PERIOD_MS); continue; } if (uart_read_bytes(ESP32_UART, &c, 1, 100) == 0) continue; #elif IS_POSIX int rc_poll = poll(fds, 1, 100); if (rc_poll == -1) { DOLOG(warning, false, "DC11 poll failed: %s", strerror(errno)); break; } if (rc_poll == 0) continue; int rc_read = read(serial_fd, &c, 1); if (rc_read <= 0) { DOLOG(warning, false, "DC11 read on %d failed: %s", serial_fd, strerror(errno)); break; } #endif std::unique_lock<std::mutex> lck(input_lock[serial_line]); recv_buffers[serial_line].push_back(c); if (serial_enabled == false && is_rx_interrupt_enabled(serial_line)) { DOLOG(debug, false, "DC11: enabling serial connection"); serial_enabled = true; // first key press enables the port registers[serial_line * 4 + 0] |= 0160000; // "ERROR", RING INDICATOR, CARRIER TRANSITION } else { TRACE("DC11: key %d pressed", c); registers[serial_line * 4 + 0] |= 128; // DONE: bit 7 } if (is_rx_interrupt_enabled(serial_line)) trigger_interrupt(serial_line, false); } TRACE("DC11: serial handler thread terminating"); } void dc11::reset() { } bool dc11::is_rx_interrupt_enabled(const int line_nr) const { return !!(registers[line_nr * 4 + 0] & 64); } bool dc11::is_tx_interrupt_enabled(const int line_nr) const { return !!(registers[line_nr * 4 + 2] & 64); } uint8_t dc11::read_byte(const uint16_t addr) { uint16_t v = read_word(addr & ~1); if (addr & 1) return v >> 8; return v; } uint16_t dc11::read_word(const uint16_t addr) { int reg = (addr - DC11_BASE) / 2; int line_nr = reg / 4; int sub_reg = reg & 3; std::unique_lock<std::mutex> lck(input_lock[line_nr]); uint16_t vtemp = registers[reg]; if (sub_reg == 0) { // receive status // emulate DTR, CTS & READY registers[line_nr * 4 + 0] &= ~1; // DTR: bit 0 [RCSR] registers[line_nr * 4 + 0] &= ~4; // CD : bit 2 if (pfds[line_nr + dc11_n_lines].fd != INVALID_SOCKET || line_nr == serial_line) { registers[line_nr * 4 + 0] |= 1; registers[line_nr * 4 + 0] |= 4; } vtemp = registers[line_nr * 4 + 0]; // clear error(s) registers[line_nr * 4 + 0] &= ~0160000; } else if (sub_reg == 1) { // read data register TRACE("DC11: %zu characters in buffer for line %d", recv_buffers[line_nr].size(), line_nr); // get oldest byte in buffer if (recv_buffers[line_nr].empty() == false) { vtemp = *recv_buffers[line_nr].begin(); // parity check registers[line_nr * 4 + 0] &= ~(1 << 5); registers[line_nr * 4 + 0] |= parity(vtemp) << 5; recv_buffers[line_nr].erase(recv_buffers[line_nr].begin()); // still data in buffer? generate interrupt if (recv_buffers[line_nr].empty() == false) { registers[line_nr * 4 + 0] |= 128; // DONE: bit 7 if (is_rx_interrupt_enabled(line_nr)) trigger_interrupt(line_nr, false); } } } else if (sub_reg == 2) { // transmit status registers[line_nr * 4 + 2] &= ~2; // CTS: bit 1 [TSCR] registers[line_nr * 4 + 2] &= ~128; // READY: bit 7 if (pfds[line_nr + dc11_n_lines].fd != INVALID_SOCKET || line_nr == serial_line) { registers[line_nr * 4 + 2] |= 2; registers[line_nr * 4 + 2] |= 128; } vtemp = registers[line_nr * 4 + 2]; } TRACE("DC11: read register %06o (\"%s\", %d line %d): %06o", addr, dc11_register_names[sub_reg], sub_reg, line_nr, vtemp); return vtemp; } void dc11::write_byte(const uint16_t addr, const uint8_t v) { uint16_t vtemp = registers[(addr - DC11_BASE) / 2]; if (addr & 1) { vtemp &= ~0xff00; vtemp |= v << 8; } else { vtemp &= ~0x00ff; vtemp |= v; } write_word(addr, vtemp); } void dc11::write_word(const uint16_t addr, const uint16_t v) { int reg = (addr - DC11_BASE) / 2; int line_nr = reg / 4; int sub_reg = reg & 3; std::unique_lock<std::mutex> lck(input_lock[line_nr]); TRACE("DC11: write register %06o (\"%s\", %d line_nr %d) to %06o", addr, dc11_register_names[sub_reg], sub_reg, line_nr, v); if (sub_reg == 3) { // transmit buffer char c = v & 127; // strip parity if (c <= 32 || c >= 127) TRACE("DC11: transmit [%d] on line %d", c, line_nr); else TRACE("DC11: transmit %c on line %d", c, line_nr); if (line_nr == serial_line) { if (serial_thread_running) { #if defined(ESP32) uart_write_bytes(ESP32_UART, &c, 1); #elif IS_POSIX if (write(serial_fd, &c, 1) != 1) { DOLOG(warning, false, "DC11 failed to send %d to (fd %d) serial port: %s", c, serial_fd, strerror(errno)); // TODO error handling } #endif } else { TRACE("DC11 serial line 4 not connected, yet output %d", c); } if (is_tx_interrupt_enabled(line_nr)) trigger_interrupt(line_nr, true); return; } SOCKET fd = pfds[dc11_n_lines + line_nr].fd; if (fd != INVALID_SOCKET && write(fd, &c, 1) != 1) { DOLOG(info, false, "DC11 line %d disconnected\n", line_nr + 1); registers[line_nr * 4 + 0] |= 0140000; // "ERROR", CARRIER TRANSITION #if IS_POSIX assert(fd != serial_fd); #endif close(fd); pfds[dc11_n_lines + line_nr].fd = INVALID_SOCKET; } if (is_tx_interrupt_enabled(line_nr)) trigger_interrupt(line_nr, true); } registers[reg] = v; }