|
| 1 | +/* A work-in-progess MEGA65 (Commodore 65 clone origins) emulator |
| 2 | + Part of the Xemu project, please visit: https://github.com/lgblgblgb/xemu |
| 3 | + Copyright (C)2025 LGB (Gábor Lénárt) <[email protected]> |
| 4 | +
|
| 5 | +This program is free software; you can redistribute it and/or modify |
| 6 | +it under the terms of the GNU General Public License as published by |
| 7 | +the Free Software Foundation; either version 2 of the License, or |
| 8 | +(at your option) any later version. |
| 9 | +
|
| 10 | +This program is distributed in the hope that it will be useful, |
| 11 | +but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | +GNU General Public License for more details. |
| 14 | +
|
| 15 | +You should have received a copy of the GNU General Public License |
| 16 | +along with this program; if not, write to the Free Software |
| 17 | +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ |
| 18 | + |
| 19 | +#ifdef XEMU_HAS_SOCKET_API |
| 20 | + |
| 21 | +#include "xemu/emutools.h" |
| 22 | +#include "serialtcp.h" |
| 23 | +#include "xemu/emutools_socketapi.h" |
| 24 | + |
| 25 | +#define UART_CLOCK 80000000 |
| 26 | +#define BUFFER_SIZE 256 |
| 27 | + |
| 28 | +static volatile xemusock_socket_t sock = XS_INVALID_SOCKET; |
| 29 | +static volatile bool running = false; |
| 30 | +static volatile bool exited = false; |
| 31 | +static SDL_Thread *thread_id = NULL; |
| 32 | +static Uint8 rx_buffer[BUFFER_SIZE]; |
| 33 | +static Uint8 tx_buffer[BUFFER_SIZE]; |
| 34 | +static SDL_atomic_t rx_fill, tx_fill; |
| 35 | +static SDL_SpinLock lock = 0; |
| 36 | +static Uint8 serial_regs[0x10]; |
| 37 | +static int bitrate_divisor, baudrate; |
| 38 | +static int bitrate_divisor_reported = -1; |
| 39 | + |
| 40 | + |
| 41 | +static void close_socket ( void ) |
| 42 | +{ |
| 43 | + if (sock != XS_INVALID_SOCKET) { |
| 44 | + int xerr; |
| 45 | + xemusock_shutdown(sock, &xerr); |
| 46 | + xemusock_close(sock, &xerr); |
| 47 | + sock = XS_INVALID_SOCKET; |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | + |
| 52 | +static int the_thread ( void *unused ) |
| 53 | +{ |
| 54 | + DEBUGPRINT("SERIALTCP: thread: begin" NL); |
| 55 | + Uint8 rx_temp[BUFFER_SIZE]; |
| 56 | + while (running) { |
| 57 | + const int tx_size = SDL_AtomicGet(&tx_fill); |
| 58 | + const int rx_size = SDL_AtomicGet(&rx_fill); |
| 59 | + const int what = ((rx_size < BUFFER_SIZE) ? XEMUSOCK_SELECT_R : 0) | (tx_size ? XEMUSOCK_SELECT_W : 0); |
| 60 | + if (!what) { |
| 61 | + SDL_Delay(10); |
| 62 | + continue; |
| 63 | + } |
| 64 | + int xerr, ret_write = -1, ret_read = -1; |
| 65 | + const int ret = xemusock_select_1(sock, 1000, what, &xerr); |
| 66 | + // TODO: error handling, end of connection / lost connection ... |
| 67 | + if ((ret & XEMUSOCK_SELECT_W)) { |
| 68 | + ret_write = xemusock_send(sock, tx_buffer, tx_size, &xerr); |
| 69 | + } |
| 70 | + if ((ret & XEMUSOCK_SELECT_R)) { |
| 71 | + ret_read = xemusock_recv(sock, rx_temp, BUFFER_SIZE - rx_size, &xerr); |
| 72 | + } |
| 73 | + if (ret_read > 0 || ret_write > 0) { |
| 74 | + SDL_AtomicLock(&lock); |
| 75 | + if (ret_read > 0) { |
| 76 | + const int size = SDL_AtomicGet(&rx_fill); |
| 77 | + memcpy(rx_buffer + size, rx_temp, ret_read); |
| 78 | + SDL_AtomicSet(&rx_fill, size + ret_read); |
| 79 | + } |
| 80 | + if (ret_write > 0) { |
| 81 | + const int size = SDL_AtomicGet(&tx_fill); |
| 82 | + if (size > ret_write) |
| 83 | + memmove(tx_buffer, tx_buffer + ret_write, size - ret_write); |
| 84 | + SDL_AtomicSet(&tx_fill, size - ret_write); |
| 85 | + } |
| 86 | + SDL_AtomicUnlock(&lock); |
| 87 | + } |
| 88 | + } |
| 89 | + DEBUGPRINT("SERIALTCP: thread: end" NL); |
| 90 | + close_socket(); |
| 91 | + exited = true; |
| 92 | + return 0; |
| 93 | +} |
| 94 | + |
| 95 | + |
| 96 | +int serialtcp_init ( const char *connection ) |
| 97 | +{ |
| 98 | + static const char error_prefix[] = "SerialTCP error:"; |
| 99 | + if (!connection || !*connection) |
| 100 | + return 0; |
| 101 | + if (running || sock != XS_INVALID_SOCKET) { |
| 102 | + ERROR_WINDOW("%s cannot init connection as it's already on-going!", error_prefix); |
| 103 | + return -1; |
| 104 | + } |
| 105 | + unsigned int ip, port; |
| 106 | + const char *errmsg = xemusock_parse_string_connection_parameters(connection, &ip, &port); |
| 107 | + if (errmsg) { |
| 108 | + ERROR_WINDOW("%s parsing: %s", error_prefix, errmsg); |
| 109 | + return -1; |
| 110 | + } |
| 111 | + if (port < 1 || port >= 0x10000 || !ip) { |
| 112 | + ERROR_WINDOW("%s parsing: bad IP and/or port: %s", error_prefix, connection); |
| 113 | + return -1; |
| 114 | + } |
| 115 | + errmsg = xemusock_init(); |
| 116 | + if (errmsg) { |
| 117 | + ERROR_WINDOW("%s network init problem: %s", error_prefix, errmsg); |
| 118 | + return -1; |
| 119 | + } |
| 120 | + int xerr; |
| 121 | + sock = xemusock_create_for_inet(XEMUSOCK_TCP, XEMUSOCK_BLOCKING, &xerr); |
| 122 | + if (sock == XS_INVALID_SOCKET) { |
| 123 | + ERROR_WINDOW("%s cannot create TCP socket: %s", error_prefix, xemusock_strerror(xerr)); |
| 124 | + return -1; |
| 125 | + } |
| 126 | + struct sockaddr_in sock_st; |
| 127 | + xemusock_fill_servaddr_for_inet_ip_netlong(&sock_st, ip, port); |
| 128 | + if (xemusock_connect(sock, &sock_st, &xerr)) { |
| 129 | + ERROR_WINDOW("%s cannot connect to TCP target: %s", error_prefix, xemusock_strerror(xerr)); |
| 130 | + close_socket(); |
| 131 | + return -1; |
| 132 | + } |
| 133 | + if (xemusock_setsockopt_keepalive(sock, &xerr)) |
| 134 | + ERROR_WINDOW("%s warning, could not set KEEPALIVE: %s", error_prefix, xemusock_strerror(xerr)); |
| 135 | + if (xemusock_set_nonblocking(sock, 1, &xerr)) { |
| 136 | + ERROR_WINDOW("%s cannot set unblock for TCP target: %s", error_prefix, xemusock_strerror(xerr)); |
| 137 | + close_socket(); |
| 138 | + return -1; |
| 139 | + } |
| 140 | + // Reset data structures |
| 141 | + SDL_AtomicSet(&rx_fill, 0); |
| 142 | + SDL_AtomicSet(&tx_fill, 0); |
| 143 | + SDL_AtomicUnlock(&lock); |
| 144 | + running = true; |
| 145 | + exited = false; |
| 146 | + memset(serial_regs, 0, sizeof serial_regs); |
| 147 | + bitrate_divisor = 0; |
| 148 | + baudrate = 19200; |
| 149 | + // Start thread |
| 150 | + thread_id = SDL_CreateThread(the_thread, "Xemu-SerialTCP", NULL); |
| 151 | + if (!thread_id) { |
| 152 | + running = false; |
| 153 | + close_socket(); |
| 154 | + ERROR_WINDOW("%s cannot create thread: %s", error_prefix, SDL_GetError()); |
| 155 | + return -1; |
| 156 | + } |
| 157 | + OSD(-1, -1, "SerialTCP connection established to\n%s", connection); |
| 158 | + return 0; |
| 159 | +} |
| 160 | + |
| 161 | + |
| 162 | +int serialtcp_shutdown ( void ) |
| 163 | +{ |
| 164 | + if (!running) { |
| 165 | + DEBUGPRINT("SERIALTCP: not running, nothing to shut down" NL); |
| 166 | + return 0; |
| 167 | + } |
| 168 | + running = false; |
| 169 | + Uint32 ticks = SDL_GetTicks(); |
| 170 | + bool ok = true; |
| 171 | + for (;;) { |
| 172 | + Uint32 elapsed = SDL_GetTicks() - ticks; |
| 173 | + if (exited) { |
| 174 | + DEBUGPRINT("SERIALTCP: thread has exited after %d ms" NL, elapsed); |
| 175 | + break; |
| 176 | + } |
| 177 | + if (elapsed > 200) { |
| 178 | + DEBUGPRINT("SERIALTCP: thread has timed out after %d ms" NL, elapsed); |
| 179 | + ok = false; |
| 180 | + break; |
| 181 | + } |
| 182 | + SDL_Delay(10); |
| 183 | + } |
| 184 | + close_socket(); // force to close - just in case ... (especially for thread timeout) |
| 185 | + return ok ? 0 : -1; |
| 186 | +} |
| 187 | + |
| 188 | + |
| 189 | +int serialtcp_restart ( const char *connection ) |
| 190 | +{ |
| 191 | + if (!connection || !*connection) { |
| 192 | + ERROR_WINDOW("Cannot restart SerialTCP: no target specification"); |
| 193 | + return -1; |
| 194 | + } |
| 195 | + DEBUGPRINT("SERIALTCP: restarting connection ..." NL); |
| 196 | + serialtcp_shutdown(); |
| 197 | + return serialtcp_init(connection); |
| 198 | +} |
| 199 | + |
| 200 | + |
| 201 | +static int send_byte ( const Uint8 byte ) |
| 202 | +{ |
| 203 | + SDL_AtomicLock(&lock); |
| 204 | + const int size = SDL_AtomicGet(&tx_fill); |
| 205 | + if (size >= BUFFER_SIZE) { |
| 206 | + SDL_AtomicUnlock(&lock); |
| 207 | + return -1; |
| 208 | + } |
| 209 | + tx_buffer[size] = byte; |
| 210 | + SDL_AtomicSet(&tx_fill, size + 1); |
| 211 | + SDL_AtomicUnlock(&lock); |
| 212 | + DEBUGPRINT("SERIALTCP: byte sent: $%02X" NL, byte); |
| 213 | + return 0; |
| 214 | +} |
| 215 | + |
| 216 | + |
| 217 | +static int recv_byte ( const bool also_remove ) |
| 218 | +{ |
| 219 | + SDL_AtomicLock(&lock); |
| 220 | + const int size = SDL_AtomicGet(&rx_fill); |
| 221 | + if (!size) { |
| 222 | + SDL_AtomicUnlock(&lock); |
| 223 | + // No is available (RX buffer is empty). What should I return now? |
| 224 | + return -1; |
| 225 | + } |
| 226 | + const Uint8 byte = rx_buffer[0]; |
| 227 | + if (also_remove) { |
| 228 | + if (size > 1) |
| 229 | + memmove(rx_buffer, rx_buffer + 1, size - 1); |
| 230 | + SDL_AtomicSet(&rx_fill, size - 1); |
| 231 | + } |
| 232 | + SDL_AtomicUnlock(&lock); |
| 233 | + DEBUGPRINT("SERIALTCP: byte %s $%02X" NL, also_remove ? "removed" : "received", byte); |
| 234 | + return byte; |
| 235 | +} |
| 236 | + |
| 237 | + |
| 238 | +static inline Uint8 get_status ( void ) |
| 239 | +{ |
| 240 | + const int rx = SDL_AtomicGet(&rx_fill); |
| 241 | + const int tx = SDL_AtomicGet(&tx_fill); |
| 242 | + Uint8 status = |
| 243 | + (tx >= BUFFER_SIZE ? 0x08 : 0x00) | // bit 3: TX buffer full |
| 244 | + (rx >= BUFFER_SIZE ? 0x10 : 0x00) | // bit 4: RX buffer full |
| 245 | + (tx == 0 ? 0x20 : 0x00) | // bit 5: TX buffer empty |
| 246 | + (rx == 0 ? 0x40 : 0x00); // bit 6: RX buffer empty |
| 247 | + return status; |
| 248 | +} |
| 249 | + |
| 250 | + |
| 251 | +static void do_report_bitrate ( void ) |
| 252 | +{ |
| 253 | + baudrate = UART_CLOCK / (bitrate_divisor + 1); |
| 254 | + DEBUGPRINT("SERIALTCP: bitrate is %d baud (divisior = %d) now" NL, baudrate, bitrate_divisor); |
| 255 | +} |
| 256 | + |
| 257 | + |
| 258 | +static XEMU_INLINE void report_bitrate ( void ) |
| 259 | +{ |
| 260 | + if (XEMU_UNLIKELY(bitrate_divisor != bitrate_divisor_reported)) { |
| 261 | + bitrate_divisor_reported = bitrate_divisor; |
| 262 | + do_report_bitrate(); |
| 263 | + } |
| 264 | +} |
| 265 | + |
| 266 | + |
| 267 | +void serialtcp_write_reg ( const int reg, const Uint8 data ) |
| 268 | +{ |
| 269 | + serial_regs[reg] = data; |
| 270 | + switch (reg) { |
| 271 | + case 2: // $D0E2: used to ACK received byte if this register is **WRITTEN** (normally this register is used to read the buffer) |
| 272 | + (void)recv_byte(true); |
| 273 | + break; |
| 274 | + case 3: // $D0E3: transmit byte |
| 275 | + report_bitrate(); |
| 276 | + send_byte(data); |
| 277 | + break; |
| 278 | + // $D0E4...$D0E6: bitrate divisor bytes |
| 279 | + case 4: |
| 280 | + case 5: |
| 281 | + case 6: |
| 282 | + bitrate_divisor = serial_regs[4] + (serial_regs[5] << 8) + (serial_regs[6] << 16); |
| 283 | + break; |
| 284 | + } |
| 285 | +} |
| 286 | + |
| 287 | + |
| 288 | +Uint8 serialtcp_read_reg ( const int reg ) |
| 289 | +{ |
| 290 | + switch (reg) { |
| 291 | + case 1: |
| 292 | + return get_status() | (serial_regs[1] & (255 - (0x08|0x10|0x20|0x40))); |
| 293 | + case 2: // $D0E2: read received byte (but do not remove from buffer) |
| 294 | + report_bitrate(); |
| 295 | + return recv_byte(false); |
| 296 | + default: |
| 297 | + return serial_regs[reg]; |
| 298 | + |
| 299 | + } |
| 300 | +} |
| 301 | + |
| 302 | +#endif |
0 commit comments