Skip to content

Commit 1ca28a0

Browse files
Add VIA2 socket support for external I/O integration
Adds optional -via2-socket support to allow external processes to communicate with the emulator via a UNIX domain socket. Includes documentation and example usage.
1 parent fdb34dd commit 1ca28a0

File tree

5 files changed

+187
-1
lines changed

5 files changed

+187
-1
lines changed

README.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ When starting `x16emu` without arguments, it will pick up the system ROM (`rom.b
128128
* `-sound <device>` can be used to specify the output sound device. If 'none', no audio is generated.
129129
* `-abufs` can be used to specify the number of audio buffers (defaults to 8 when using the SD card, 32 when using HostFS). If you're experiencing stuttering in the audio, try increasing this number. This will result in additional audio latency though.
130130
* `-via2` installs the second VIA chip expansion at $9F10.
131+
* `-via2-socket <socket>` Use a socket to emulate VIA2 I/O. Currently only UNIX / POSIX are supported. The emulator opens this socket as a client, so it must be set up in advance. The -via2 option must be specified along with this option. See [Using -via2-socket](#via2-socket) for sample server and client code.
131132
* `-midline-effects` enables mid-scanline raster effects at the cost of vastly increased host CPU usage.
132133
* `-mhz <integer>` sets the emulated CPU's speed. Range is from 1-40. This option is mainly for testing and benchmarking.
133134
* `-enable-ym2151-irq` connects the YM2151's IRQ pin to the system's IRQ line with a modest increase in host CPU usage.
@@ -450,6 +451,113 @@ When `-debug` is selected the STP instruction (opcode $DB) will break into the d
450451

451452
Keyboard routines only work when the emulator is running normally. Single stepping through keyboard code will not work at present.
452453

454+
<a name="via2-socket"></a>
455+
Using -via2-socket
456+
------------------
457+
458+
Reads from and writes to both `$9F10` and `$9F11` (VIA ports A and B) are forwarded to the socket.
459+
460+
There are no restrictions to the protocols used except that a value of `255` is reserved for "no data yet" when reading in the emulator.
461+
462+
The following example demonstrates a simple bidirectional chat interface implemented using the `-via2-socket` option. It uses a protocol where `0` indicates the end of a line. Note the special handling of `255` in the client.
463+
464+
### Sample server (Python)
465+
466+
Save as `via2.py`:
467+
468+
```python
469+
#!/usr/bin/env python3
470+
import os
471+
import socket
472+
import subprocess
473+
import sys
474+
475+
SOCKET_PATH = "/tmp/via2.sock"
476+
CHT_EOL = b'\x00'
477+
478+
# Remove old socket if present
479+
try:
480+
os.unlink(SOCKET_PATH)
481+
except FileNotFoundError:
482+
pass
483+
484+
# Create UNIX domain socket
485+
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
486+
server.bind(SOCKET_PATH)
487+
server.listen(1)
488+
489+
print("Waiting for emulator...")
490+
491+
# Launch emulator AFTER socket is ready
492+
subprocess.Popen([
493+
"./x16emu",
494+
"-scale", "2",
495+
"-via2",
496+
"-via2-socket", SOCKET_PATH,
497+
"-bas", "VIA2.BAS",
498+
"-run"
499+
], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
500+
501+
# Accept connection
502+
conn, _ = server.accept()
503+
print("Emulator connected.")
504+
505+
try:
506+
while True:
507+
# ---- RECEIVE STRING ----
508+
buf = bytearray()
509+
while True:
510+
b = conn.recv(1)
511+
if not b:
512+
raise ConnectionError("Socket closed")
513+
if b == CHT_EOL:
514+
break
515+
buf += b
516+
517+
text = buf.decode(errors="replace")
518+
print("> " + text)
519+
520+
if text == "":
521+
break
522+
523+
# ---- SEND RESPONSE ----
524+
reply = input("MESSAGE? ").upper().encode() + CHT_EOL
525+
conn.sendall(reply)
526+
527+
finally:
528+
try:
529+
conn.close()
530+
except Exception:
531+
pass
532+
server.close()
533+
try:
534+
os.unlink(SOCKET_PATH)
535+
except FileNotFoundError:
536+
pass
537+
```
538+
539+
### Sample client (Commander X16 BASIC)
540+
541+
Save as `VIA2.BAS`:
542+
543+
```basic
544+
100 REM VIA2 ACTS AS A BYTE-SERIAL DEVICE VIA SOCKET BRIDGE
545+
110 T$ = ""
546+
120 INPUT "MESSAGE (EMPTY LINE TO QUIT)"; T$
547+
130 REM WRITE T$ TO VIA2
548+
140 IF T$ = "" THEN GOTO 160
549+
150 FOR I = 1 TO LEN(T$): POKE $9F10, ASC(MID$(T$, I, 1)): NEXT I
550+
160 POKE $9F10, 0: REM END OF LINE
551+
170 IF T$ = "" THEN END
552+
180 REM READ T$ FROM VIA2
553+
190 T$ = ""
554+
200 B = PEEK($9F10)
555+
210 IF B = 255 THEN FOR I = 1 TO 20: NEXT I: GOTO 200: REM NO DATA YET
556+
220 IF B = 0 THEN GOTO 240: REM END OF LINE
557+
230 T$ = T$ + CHR$(B): GOTO 200
558+
240 PRINT "> "; T$: GOTO 110
559+
```
560+
453561
CRT File Format
454562
---------------
455563

src/glue.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ extern bool warp_mode;
8080
extern bool grab_mouse;
8181
extern bool testbench;
8282
extern bool has_via2;
83+
extern char *via2_socket;
8384
extern uint32_t host_sample_rate;
8485
extern bool enable_midline;
8586

src/main.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ bool set_system_time = false;
115115
bool has_serial = false;
116116
bool no_ieee_intercept = false;
117117
bool has_via2 = false;
118+
char *via2_socket = NULL;
118119
gif_recorder_state_t record_gif = RECORD_GIF_DISABLED;
119120
char *gif_path = NULL;
120121
char *wav_path = NULL;
@@ -514,6 +515,11 @@ usage()
514515
printf("\tSet the real-time-clock to the current system time and date.\n");
515516
printf("-via2\n");
516517
printf("\tInstall the second VIA chip expansion at $9F10\n");
518+
printf("-via2-socket <socket>\n");
519+
printf("\tUse a socket to emulate VIA2 I/O. Currently only UNIX / POSIX are\n");
520+
printf("\tsupported. The emulator opens this socket as a client, so it must be\n");
521+
printf("\tset up in advance. The -via2 option must be specified along with\n");
522+
printf("\tthis option.\n");
517523
printf("-testbench\n");
518524
printf("\tHeadless mode for unit testing with an external test runner\n");
519525
printf("-mhz <integer>\n");
@@ -1088,6 +1094,15 @@ main(int argc, char **argv)
10881094
argc--;
10891095
argv++;
10901096
has_via2 = true;
1097+
} else if (!strcmp(argv[0], "-via2-socket")) {
1098+
argc--;
1099+
argv++;
1100+
if (!argc || argv[0][0] == '-') {
1101+
usage();
1102+
}
1103+
via2_socket = argv[0];
1104+
argc--;
1105+
argv++;
10911106
} else if (!strcmp(argv[0], "-version")){
10921107
printf("%s", VER_INFO);
10931108
#ifdef GIT_REV
@@ -1310,6 +1325,7 @@ void main_shutdown() {
13101325
cartridge_unload();
13111326
}
13121327
files_shutdown();
1328+
via2_shutdown();
13131329

13141330
#ifdef PERFSTAT
13151331
for (int pc = 0xc000; pc < sizeof(stat)/sizeof(*stat); pc++) {

src/via.c

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ typedef struct {
2525

2626
static via_t via[2];
2727

28+
#define VIA2_SOCKET_ERROR 0xFF
29+
30+
#ifdef _WIN32
31+
// Windows not yet supported
32+
#else // UNIX / POSIX
33+
#include <sys/socket.h>
34+
#include <sys/un.h>
35+
#include <unistd.h>
36+
static int via2_socket_fd = -1;
37+
#endif
38+
2839
// only internal logic is handled here, see via1/2 calls for external
2940
// operations specific to each unit
3041

@@ -335,18 +346,56 @@ void
335346
via2_init()
336347
{
337348
via_init(&via[1]);
349+
if (via2_socket) {
350+
#ifdef _WIN32
351+
// Windows not yet supported
352+
via2_socket = NULL;
353+
activity_led = VIA2_SOCKET_ERROR;
354+
#else // UNIX / POSIX
355+
struct sockaddr_un addr;
356+
memset(&addr, 0, sizeof(addr));
357+
addr.sun_family = AF_UNIX;
358+
strncpy(addr.sun_path, via2_socket, sizeof(addr.sun_path) - 1);
359+
if ((via2_socket_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 || connect(via2_socket_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
360+
via2_shutdown();
361+
// Clear to prevent socket I/O
362+
via2_socket = NULL;
363+
activity_led = VIA2_SOCKET_ERROR;
364+
}
365+
#endif
366+
}
338367
}
339368

340369
uint8_t
341370
via2_read(uint8_t reg, bool debug)
342371
{
343-
return via_read(&via[1], reg, debug);
372+
uint8_t byte = via_read(&via[1], reg, debug);
373+
if (via2_socket && reg <= 1) {
374+
#ifdef _WIN32
375+
// Windows not yet supported
376+
#else // UNIX / POSIX
377+
if (recv(via2_socket_fd, &byte, 1, MSG_DONTWAIT) <= 0) {
378+
// Use 0xFF for no data
379+
byte = 0xFF;
380+
}
381+
#endif
382+
}
383+
return byte;
344384
}
345385

346386
void
347387
via2_write(uint8_t reg, uint8_t value)
348388
{
349389
via_write(&via[1], reg, value);
390+
if (via2_socket && reg <= 1) {
391+
#ifdef _WIN32
392+
// Windows not yet supported
393+
#else // UNIX / POSIX
394+
if (send(via2_socket_fd, &value, 1, 0) <= 0) {
395+
activity_led = VIA2_SOCKET_ERROR;
396+
}
397+
#endif
398+
}
350399
}
351400

352401
void
@@ -360,3 +409,14 @@ via2_irq()
360409
{
361410
return (via[1].registers[13] & via[1].registers[14]) != 0;
362411
}
412+
413+
void
414+
via2_shutdown()
415+
{
416+
#ifdef _WIN32
417+
// Windows not yet supported
418+
#else // UNIX / POSIX
419+
close(via2_socket_fd);
420+
via2_socket_fd = -1;
421+
#endif
422+
}

src/via.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ uint8_t via2_read(uint8_t reg, bool debug);
1919
void via2_write(uint8_t reg, uint8_t value);
2020
void via2_step(unsigned clocks);
2121
bool via2_irq();
22+
void via2_shutdown();
2223

2324
#endif

0 commit comments

Comments
 (0)