Skip to content

Commit ecd05eb

Browse files
committed
MEGA65: preliminary serial-tcp support #440
Trying to emulate MEGA65's buffered UART in the form of a TCP connection to a given target.
1 parent 41c8a4c commit ecd05eb

File tree

10 files changed

+442
-11
lines changed

10 files changed

+442
-11
lines changed

targets/mega65/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ TARGET = mega65
2020
PRG_TARGET = xmega65
2121
EMU_DESCRIPTION = MEGA65
2222

23-
SRCS_TARGET_xmega65 = configdb.c mega65.c sdcard.c uart_monitor.c hypervisor.c memory_mapper.c io_mapper.c vic4.c vic4_palette.c ethernet65.c input_devices.c memcontent.c ui.c fat32.c sdcontent.c audio65.c inject.c dma65.c rom.c hdos.c matrix_mode.c cart.c
23+
SRCS_TARGET_xmega65 = configdb.c mega65.c sdcard.c uart_monitor.c hypervisor.c memory_mapper.c io_mapper.c vic4.c vic4_palette.c ethernet65.c input_devices.c memcontent.c ui.c fat32.c sdcontent.c audio65.c inject.c dma65.c rom.c hdos.c matrix_mode.c cart.c serialtcp.c
2424
SRCS_COMMON_xmega65 = emutools.c cpu65.c cia6526.c emutools_hid.c sid.c f011_core.c c64_kbd_mapping.c emutools_config.c emutools_files.c emutools_umon.c emutools_socketapi.c ethertap.c d81access.c emutools_gui.c basic_text.c opl3.c lodepng.c compressed_disk_image.c cpu65_disasm.c emutools_osk.c
2525
CFLAGS_TARGET_xmega65 = $(SDL2_CFLAGS) $(MATH_CFLAGS) $(SOCKET_CFLAGS) $(XEMUGUI_CFLAGS)
2626
LDFLAGS_TARGET_xmega65 = $(SDL2_LIBS) $(MATH_LIBS) $(SOCKET_LIBS) $(XEMUGUI_LIBS)

targets/mega65/configdb.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ static const struct xemutools_configdef_str_st str_options[] = {
7777
{ "cart", NULL, "Load cartridge file", &configdb.cart },
7878
{ "winpos", NULL, "Window position: x,y (integers)", &configdb.winpos },
7979
{ "initattic", NULL, "Pre-fill the Attic RAM with the content of a file", &configdb.init_attic },
80+
#ifdef XEMU_HAS_SOCKET_API
81+
{ "serialtcp", NULL, "HOST:PORT for serial emulation", &configdb.serialtcp },
82+
#endif
8083
{ NULL }
8184
};
8285

targets/mega65/configdb.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ struct configdb_st {
124124
#endif
125125
int resethotkeytype;
126126
int realhw;
127+
#ifdef XEMU_HAS_SOCKET_API
128+
char *serialtcp;
129+
#endif
127130
};
128131

129132
extern struct configdb_st configdb;

targets/mega65/io_mapper.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
3232
#include "audio65.h"
3333
#include "configdb.h"
3434
#include "mega65.h"
35+
#include "serialtcp.h"
3536

3637

3738
int fpga_switches = 0; // State of FPGA board switches (bits 0 - 15), set switch 12 (hypervisor serial output)
@@ -226,8 +227,12 @@ Uint8 io_read ( unsigned int addr )
226227
return vic_read_reg(addr); // VIC-IV read register
227228
if (addr == 0x8F)
228229
return hw_errata_level;
229-
if (XEMU_LIKELY(addr < 0xA0))
230+
if (addr < 0xA0)
230231
return fdc_read_reg(addr & 0xF);
232+
# ifdef XEMU_HAS_SOCKET_API
233+
if (addr >= 0xE0 && addr <= 0xE6)
234+
return serialtcp_read_reg(addr & 0xF);
235+
# endif
231236
RETURN_ON_IO_READ_NOT_IMPLEMENTED("RAM expansion controller", 0xFF);
232237
case 0x11: // $D100-$D1FF ~ C65 I/O mode
233238
case 0x12: // $D200-$D2FF ~ C65 I/O mode
@@ -472,10 +477,16 @@ void io_write ( unsigned int addr, Uint8 data )
472477
set_hw_errata_level(data, "D08F change");
473478
return;
474479
}
475-
if (XEMU_LIKELY(addr < 0xA0)) {
480+
if (addr < 0xA0) {
476481
fdc_write_reg(addr & 0xF, data);
477482
return;
478483
}
484+
# ifdef XEMU_HAS_SOCKET_API
485+
if (addr >= 0xE0 && addr <= 0xE6) {
486+
serialtcp_write_reg(addr & 0xF, data);
487+
return;
488+
}
489+
# endif
479490
RETURN_ON_IO_WRITE_NOT_IMPLEMENTED("RAM expansion controller");
480491
case 0x11: // $D100-$D1FF ~ C65 I/O mode
481492
vic3_write_palette_reg_red(addr, data); // function takes care using only 8 low bits of addr, no need to do here

targets/mega65/mega65.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
4444
#include "cart.h"
4545
#include "matrix_mode.h"
4646
#include "xemu/emutools_osk.h"
47+
#include "serialtcp.h"
4748

4849
// "Typical" size in default settings (video standard is PAL, default border settings).
4950
// See also vic4.h
@@ -501,6 +502,7 @@ static void shutdown_callback ( void )
501502
xumon_stop();
502503
#endif
503504
#ifdef XEMU_HAS_SOCKET_API
505+
serialtcp_shutdown();
504506
xemusock_uninit();
505507
#endif
506508
hypervisor_hdos_close_descriptors();
@@ -820,6 +822,9 @@ int main ( int argc, char **argv )
820822
if (!configdb.syscon)
821823
sysconsole_close(NULL);
822824
hypervisor_serial_monitor_open_file(configdb.hyperserialfile);
825+
#ifdef XEMU_HAS_SOCKET_API
826+
serialtcp_init(configdb.serialtcp);
827+
#endif
823828
xemu_timekeeping_start();
824829
emulation_is_running = 1;
825830
update_emulated_time_sources();

targets/mega65/serialtcp.c

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
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

Comments
 (0)