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);
+}