diff options
| author | user <user@clank> | 2026-09-24 23:08:54 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-24 23:08:54 +0200 |
| commit | be1a8f74e48d4ed2ae3a0b78a7137b4bc41a2969 (patch) | |
| tree | 25477cc16331b6e919deab918eecfcc4edebc0cb | |
| parent | usr: sort, cmp, strings, du, time (diff) | |
| download | gbos-be1a8f74e48d4ed2ae3a0b78a7137b4bc41a2969.tar.gz gbos-be1a8f74e48d4ed2ae3a0b78a7137b4bc41a2969.tar.xz gbos-be1a8f74e48d4ed2ae3a0b78a7137b4bc41a2969.zip | |
An MBC5 cart has no RTC, so SNTP is the only route to a wall clock. ntpdate
does one 48-byte UDP exchange on port 123 and hands the stamp to the kernel;
SYS_SETTIME keeps it with the tick it arrived at, and SYS_GETTIME adds the
elapsed ticks back, so date(1) is just formatting.
/# ntpdate
ntpdate: clock set from pool.ntp.org
/# date
Sep 24 21:01:23 UTC 2026 (host said 21:01:26)
The clock does not survive a power cycle - which is exactly what /rc is for:
`ntpdate` there and the machine boots knowing the time.
All of it is bounded subtraction, no long division or shifts: SDCC pulls those
from sm83.lib, whose modules link into the $A000 RAM window. The month tables
are string literals for the same family of reason - an initialized array goes
to _INITIALIZED at $A000 and the image overflows its bank ("buffer too small"
out of makebin), which is how this first failed to link.
| -rw-r--r-- | README.md | 12 | ||||
| -rw-r--r-- | sl0ppty.layout.kdl | 15 | ||||
| -rw-r--r-- | slosh.layout | 8 | ||||
| -rw-r--r-- | usr/date.c | 87 | ||||
| -rw-r--r-- | usr/ntpdate.c | 81 |
5 files changed, 199 insertions, 4 deletions
@@ -22,7 +22,10 @@ into the on-screen console. Regenerate it anytime with `tools/gbdemo` (below).* `more` to page anything longer than the 18-row screen. - **Shell scripts**: a shell fed from a file (`sh < script`) runs it with no prompt and no echo, and init runs **`/rc`** at boot if the cart has one — - so a Game Boy can come up with its own startup script. + so a Game Boy can come up with its own startup script (`dhcp`, then + `ntpdate`, and it knows the date). +- **A real clock**: no RTC on the cart, so `ntpdate` fetches the time over + SNTP and `date` prints it — verified against the host's clock to the second. - **Networking, for real**: the kernel owns SLIP over the link port, IPv4, ICMP, UDP and TCP; userland gets BSD-ish sockets via one syscall. On top: `dhcp` (boot-time lease), `ping`, `nslookup` (real DNS), `wget` (HTTP), @@ -44,7 +47,8 @@ into the on-screen console. Regenerate it anytime with `tools/gbdemo` (below).* - **40×18 color terminal** on the LCD (4×8 font, two chars per tile, ANSI-ish colors) + an **on-screen keyboard** on the window layer (SELECT toggles it). - **Persistent filesystem** on battery-backed cart RAM: inodes, nested - directories, per-process cwd — survives power-off (files up to 3 KiB). + directories, hard links (`ln`), per-process cwd — survives power-off + (files up to 3 KiB). `ls -l` and `df` report sizes and free space; `poweroff` is the only safe way to stop the machine (the battery save is written on a clean exit). - **`/bin` in the tree**: the builtins are not files on the disk, but they @@ -53,8 +57,8 @@ into the on-screen console. Regenerate it anytime with `tools/gbdemo` (below).* bank. Read-only, since it is ROM. - **A C toolchain**: userland is plain C compiled with SDCC (`sm83`); ~50 tools ship in `usr/` (`cat`, `wc`, `head`, `tail`, `grep`, `cp`, `mv`, - `tee`, `tr`, `cut`, `uniq`, `rev`, `more`, `ls`, `df`, `xxd`, - `touch`, `sleep`, `uptime`, …) — `help` lists them all on + `tee`, `tr`, `cut`, `uniq`, `rev`, `sort`, `cmp`, `strings`, `more`, `ls`, + `ln`, `df`, `du`, `xxd`, `touch`, `sleep`, `time`, `clear`, `uptime`, …) — `help` lists them all on the Game Boy itself. Adding a program is one `.c` file + a table entry. ## Quick start diff --git a/sl0ppty.layout.kdl b/sl0ppty.layout.kdl new file mode 100644 index 0000000..f767add --- /dev/null +++ b/sl0ppty.layout.kdl @@ -0,0 +1,15 @@ +// What this project needs open, written by `save-workspace`. +// Checked by `sl0ppty --check`; opened by `open-workspace`. +layout { + tab name="gbos" split="cols" { + pane weight=2600 split="rows" { + pane weight=1000 cwd="." command="omp -c" purpose="agent:main" suspended=true focus=true + pane weight=410 cwd="." purpose="shell:gbos" + } + pane weight=2200 split="rows" { + pane weight=3000 cwd="." command="~/dev/gbc/build/sl0pboy --kitty 3 --kitty-b64 --sock /tmp/sl0pboy.sock gbos.gb" purpose="service:emu" suspended=true + pane weight=1000 cwd="." command="make" purpose="task:build" suspended=true + pane weight=800 cwd="." command="sudo tools/gbhub --daemon" purpose="service:gbhub" suspended=true + } + } +} diff --git a/slosh.layout b/slosh.layout new file mode 100644 index 0000000..b00c43c --- /dev/null +++ b/slosh.layout @@ -0,0 +1,8 @@ +// What this project needs open, written by `save-workspace`. +// Checked by `slosh --check`; opened by `open-workspace`. +layout { + tab name="gbos" split="cols" { + pane weight=1000 cwd="." + pane weight=1000 cwd="." command="tig" suspended=true focus=true + } +} diff --git a/usr/date.c b/usr/date.c new file mode 100644 index 0000000..57108ab --- /dev/null +++ b/usr/date.c @@ -0,0 +1,87 @@ +#include "gbos.h" +/* date: print the wall clock, UTC. + * + * The kernel hands back unix seconds (SYS_GETTIME); turning that into a date + * is all bounded subtraction - no long division or shifts, which SDCC would + * pull out of sm83.lib and link into the $A000 RAM window (see usr/uptime.c + * and usr/build.sh). ~20k rounds for the day loop is a fraction of a second. + * + * Says so plainly when the clock has never been set: run ntpdate first. */ + +/* No initialized arrays: SDCC puts those in _INITIALIZED at $A000, the RAM + window, and the image then overflows its bank ("buffer too small" out of + makebin - see usr/ansi.c and usr/build.sh). String literals live in _CODE, + so the tables are literals indexed by hand. */ +#define MDAYS "\x1f\x1c\x1f\x1e\x1f\x1e\x1f\x1f\x1e\x1f\x1e\x1f" +#define MNAME "JanFebMarAprMayJunJulAugSepOctNovDec" + +static void two(unsigned int v) { + if (v < 10) putc('0'); + putu(v); +} +static unsigned char leap(unsigned int y) { + if (y & 3) return 0; + if (y == 2100) return 0; /* good enough until then */ + return 1; +} + +void main(void) { + unsigned char t[4]; + union { + unsigned long l; + unsigned char b[4]; + } u; + unsigned long sec; + unsigned int days = 0, year = 1970, h, m, i, dim; + + if (!gettime(t)) { + puts("date: clock not set (run ntpdate)"); + nl(); + sexit(1); + } + u.b[0] = t[0]; + u.b[1] = t[1]; + u.b[2] = t[2]; + u.b[3] = t[3]; + sec = u.l; + + while (sec >= 86400UL) { + sec -= 86400UL; + days++; + } + h = 0; + while (sec >= 3600UL) { + sec -= 3600UL; + h++; + } + m = 0; + while (sec >= 60UL) { + sec -= 60UL; + m++; + } + + for (;;) { /* years, then months */ + unsigned int len = leap(year) ? 366 : 365; + if (days < len) break; + days -= len; + year++; + } + for (i = 0; i < 12; i++) { + dim = (unsigned char)MDAYS[i]; + if (i == 1 && leap(year)) dim = 29; + if (days < dim) break; + days -= dim; + } + writes(MNAME + i * 3, 3); + putc(' '); + putu(days + 1); + putc(' '); + two(h); + putc(':'); + two(m); + putc(':'); + two((unsigned int)sec); + puts(" UTC "); + putu(year); + nl(); +} diff --git a/usr/ntpdate.c b/usr/ntpdate.c new file mode 100644 index 0000000..bbeeb92 --- /dev/null +++ b/usr/ntpdate.c @@ -0,0 +1,81 @@ +#include "resolve.h" +/* ntpdate [SERVER]: set the clock from SNTP (default pool.ntp.org). + * + * An MBC5 cart has no RTC, so this is the only way gbos can know the real + * time. One 48-byte UDP exchange with port 123: the reply's transmit + * timestamp is seconds since 1900, and unix time is that minus 2208988800. + * The kernel keeps the stamp plus the tick it arrived at (SYS_SETTIME), so + * date(1) can add the elapsed ticks back. + * + * The clock does not survive a power cycle - put ntpdate in /rc. */ + +#define NTP_PORT 123 +#define UNIX_EPOCH_HI 0x8383 /* 2208988800 = 0x83AA7E80 */ + +static unsigned char pkt[48]; +static unsigned char namebuf[64]; +static unsigned char srv[4]; +static unsigned char now[4]; + +void main(void) { + char args[40]; + unsigned char s, n, i; + + { + char *a = getargs(); + for (i = 0; a[i] && i < 39; i++) args[i] = a[i]; + args[i] = 0; + } + if (!args[0]) { + const char *d = "pool.ntp.org"; + for (i = 0; d[i]; i++) args[i] = d[i]; + args[i] = 0; + } + for (i = 0; args[i] && i < 63; i++) namebuf[i] = (unsigned char)args[i]; + namebuf[i] = 0; + + if (!resolve((char *)namebuf, srv)) { + puts("ntpdate: cannot resolve "); + puts((char *)namebuf); + nl(); + sexit(1); + } + for (i = 0; i < 48; i++) pkt[i] = 0; + pkt[0] = 0x1B; /* LI=0, VN=3, mode=3 (client) */ + + s = net_socket(SOCK_UDP); + if (s == 0xFF) { + puts("ntpdate: no socket"); + nl(); + sexit(1); + } + net_bind(s, 40123); + net_connect(s, srv, NTP_PORT); + net_send(s, pkt, 48); + n = net_recv(s, pkt, 48); + net_close(s); + if (n == 0xFF || n < 48) { + puts("ntpdate: no reply"); + nl(); + sexit(1); + } + + /* transmit timestamp: seconds since 1900 at bytes 40..43, big-endian. + unix = ntp - 2208988800 ($83AA7E80), done bytewise - no long math. */ + { + unsigned char b0 = pkt[43], b1 = pkt[42], b2 = pkt[41], b3 = pkt[40]; + unsigned int t; + t = (unsigned int)b0 - 0x80; + now[0] = (unsigned char)t; + t = (unsigned int)b1 - 0x7E - ((t >> 8) & 1); + now[1] = (unsigned char)t; + t = (unsigned int)b2 - 0xAA - ((t >> 8) & 1); + now[2] = (unsigned char)t; + t = (unsigned int)b3 - 0x83 - ((t >> 8) & 1); + now[3] = (unsigned char)t; + } + settime(now); + puts("ntpdate: clock set from "); + puts((char *)namebuf); + nl(); +} |
