code: plan9front

Download patch

ref: e6c6217b35c319127f0200fdb28ec86e1b774a4f
parent: f5eaa9b867e46ce946734026ac4e38640a477197
author: Jacob Moody <moody@posixcafe.org>
date: Fri Feb 24 01:53:14 EST 2023

games/gb: crude serial port emulation

Timing is not as good as it needs to be,
but servicable in more forgiving scenarios.
Clock drift between two paired systems sits
around 8 - 32 cycles when tested locally.

--- a/sys/man/1/nintendo
+++ b/sys/man/1/nintendo
@@ -12,6 +12,9 @@
 ] [
 .B -x
 .I scale
+] [
+.B -li
+.I addr
 ]
 .I romfile
 .br
@@ -103,6 +106,14 @@
 .TP
 .BI -C nnnnnn,nnnnnn,nnnnnn,nnnnnn
 Select a color palette. Has no effect on roms in color mode. The syntax is of the form -C ffffff,aaaaaa,555555,000000 (using HTML style rrggbb notation).
+.TP
+.BI -li addr
+These enable serial port connectivity emulation using the network. The
+.B -l
+flag configures the listening partner, whereas
+.B -i
+configures the dialing partner. The connection is established before the rom starts.
+Communication is synchronous, emulation will stall while waiting for the other partner.
 .PP
 .B gba
 options:
@@ -163,6 +174,8 @@
 scaling.
 .br
 All emulators assume a North American (i.e. NTSC) system. PAL games (and in some cases Japanese games) are not supported.
+.br
+The serial link emulation has issues with desynchronization in timing dependent scenarios.
 .SH HISTORY
 .I Gb
 first appeared in 9front (April, 2012).
--- a/sys/src/games/gb/dat.h
+++ b/sys/src/games/gb/dat.h
@@ -151,6 +151,6 @@
 };
 #define VAR(a) {&a, sizeof(a), 1}
 #define ARR(a) {a, sizeof(*a), nelem(a)}
-enum { NEVENT = 8 };
+enum { NEVENT = 9 };
 extern int (*mapper)(int, int);
 extern u32int moncols[4];
--- a/sys/src/games/gb/ev.c
+++ b/sys/src/games/gb/ev.c
@@ -6,7 +6,8 @@
 
 Event evhblank, evtimer, evenv;
 extern Event evsamp, chev[4];
-Event *events[NEVENT] = {&evhblank, &evtimer, &evenv, &evsamp, &chev[0], &chev[1], &chev[2], &chev[3]};
+extern Event evse;
+Event *events[NEVENT] = {&evhblank, &evtimer, &evenv, &evsamp, &chev[0], &chev[1], &chev[2], &chev[3], &evse};
 Event *elist;
 static int timshtab[4] = {10, 4, 6, 8}, timsh;
 ulong timclock;
--- a/sys/src/games/gb/fns.h
+++ b/sys/src/games/gb/fns.h
@@ -26,3 +26,5 @@
 u8int waveread(u8int);
 void wavewrite(u8int, u8int);
 u8int timread(void);
+void serialwrite(void);
+void serialinit(int, char *);
--- a/sys/src/games/gb/gb.c
+++ b/sys/src/games/gb/gb.c
@@ -262,6 +262,8 @@
 threadmain(int argc, char **argv)
 {
 	int t;
+	int lis = -1;
+	char *addr = nil;
 
 	colinit();
 	ARGBEGIN {
@@ -280,11 +282,22 @@
 	case 'x':
 		fixscale = strtol(EARGF(usage()), nil, 0);
 		break;
+	case 'l':
+		lis = 1;
+		addr = EARGF(usage());
+		break;
+	case 'i':
+		lis = 0;
+		addr = EARGF(usage());
+		break;
 	default:
 		usage();
 	} ARGEND;
 	if(argc < 1)
 		usage();
+
+	if(lis == 0 || lis == 1)
+		serialinit(lis, addr);
 
 	loadrom(argv[0]);
 	initemu(PICW, PICH, 4, XRGB32, 1, nil);
--- a/sys/src/games/gb/mem.c
+++ b/sys/src/games/gb/mem.c
@@ -83,7 +83,11 @@
 
 	switch(a){
 	case JOYP: v |= 0xcf; break;
-	case SC: v |= 0x7c; break;
+	case SC:
+		if((reg[a] & 0x80) == 0 && (v & 0x80))
+			serialwrite();
+		reg[a] = v;
+		return;
 	case DIV:
 		divclock = clock;
 		v = 0;
--- a/sys/src/games/gb/mkfile
+++ b/sys/src/games/gb/mkfile
@@ -11,6 +11,7 @@
 	state.$O\
 	apu.$O\
 	eui.$O\
+	serial.$O\
 
 HFILES=dat.h fns.h
 
--- /dev/null
+++ b/sys/src/games/gb/serial.c
@@ -1,0 +1,142 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include "../eui.h"
+#include "dat.h"
+#include "fns.h"
+
+static int infd = -1;
+static int outfd = -1;
+static int leader = 0;
+
+static Channel *inc = nil;
+static Channel *outc = nil;
+
+typedef struct Msg Msg;
+struct Msg {
+	u8int c;
+	u8int b;
+	u32int t;
+};
+
+enum{
+	Cex = 1,
+	Csync = 2,
+	Cgone = 0xFF,
+};
+
+static Msg mine = {Cgone};
+static Msg theirs = {Cgone};
+Event evse;
+
+static void
+writer(void *)
+{
+	Msg m;
+	uchar buf[1+1+4];
+
+	while(recv(outc, &m) == 1){
+		buf[0] = m.c;
+		buf[1] = m.b;
+		buf[2] = m.t>>24;
+		buf[3] = m.t>>16;
+		buf[4] = m.t>>8;
+		buf[5] = m.t;
+		if(write(outfd, buf, sizeof buf) != sizeof buf)
+			break;
+	}
+
+	chanclose(outc);
+	threadexits(nil);
+}
+
+static void
+reader(void *)
+{
+	Msg m;
+	uchar buf[1+1+4];
+
+	while(readn(infd, buf, sizeof buf) == sizeof buf){
+		m.c = buf[0];
+		m.b = buf[1];
+		m.t = ((u32int)buf[2]<<24) | ((u32int)buf[3]<<16) | ((u32int)buf[4]<<8) | (u32int)buf[5];
+		send(inc, &m);
+	}
+
+	chanclose(inc);
+	threadexits(nil);
+}
+
+void
+serialwrite(void)
+{
+	if(outc == nil)
+		return;
+	mine = (Msg){Cex, reg[SB], clock};
+}
+
+void
+serialtick(void *)
+{
+	Msg m, t;
+
+	if(leader){
+		m = (Msg){mine.c == Cgone ? Csync : mine.c, mine.b, clock};
+		if(send(outc, &m) != 1)
+			return;
+		if(recv(inc, &t) != 1)
+			return;
+		theirs = t;
+	} else {
+		if(recv(inc, &t) != 1)
+			return;
+		theirs = t;
+		m = (Msg){theirs.c, reg[SB], clock};
+		if(send(outc, &m) != 1)
+			return;
+	}
+
+	addevent(&evse, FREQ / 64);
+	assert(theirs.c != Cgone);
+	if(theirs.c == Csync)
+		return;
+
+	reg[SB] = theirs.b;
+	reg[SC] &= ~0x80;
+	if(leader)
+		reg[SC] |= 0x1;
+	else
+		reg[SC] &= ~0x1;
+	reg[IF] |= 0x8;
+}
+
+void
+serialinit(int dolis, char *addr)
+{
+	int acfd, lcfd;
+	char adir[40], ldir[40];
+
+	evse.f = serialtick;
+	addevent(&evse, FREQ / 64);
+
+	if(dolis){
+		acfd = announce(addr, adir);
+		if(acfd < 0)
+			sysfatal("failed to announce: %r");
+		lcfd = listen(adir, ldir);
+		if(lcfd < 0)
+			sysfatal("failed to listen: %r");
+		infd = outfd = accept(lcfd, ldir);
+		leader = 1;
+	} else {
+		infd = outfd = dial(addr, nil, nil, nil);
+	}
+
+	if(infd < 0 || outfd < 0)
+		sysfatal("failed to establish link: %r");
+
+	inc = chancreate(sizeof(Msg), 0);
+	outc = chancreate(sizeof(Msg), 0);
+	proccreate(reader, nil, mainstacksize);
+	proccreate(writer, nil, mainstacksize);
+}