| Commit message (Collapse) | Author | Age |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Four things userland had no way to ask for:
- SYS_STAT: type, link count, size and inode for a path. The trap needs HL
for its dispatch table, so a call taking both a path and an output buffer
passes a request block - the shape SYS_NET already established. usr/stat.h
wraps it, header-only like sock.h, because the block must live in the
caller's RAM.
- SYS_FSSTAT: blocks/inodes, total and free. The bitmaps are already cached
write-through in fs_bmbuf, so this is the popcount boot_fs already prints.
- SYS_ISATTY: is this process's stdin the console? The shell needs it to tell
an interactive session from a script.
- O_APPEND: open a file positioned at EOF. There is no seek syscall, so this
is the only way to add to a file - without it nothing running ON the Game
Boy can build a multi-line file, shell scripts included.
Together these back ls -l, df, and `>>`.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/# ircd &
host$ irssi -c 10.0.0.2 /join #gbos
other GB$ irc 10.0.0.2 gb2 /join #gbos
Implements the subset a real client needs to get in and talk: NICK, USER,
PING/PONG, JOIN, PART, PRIVMSG/NOTICE (to a channel or to a nick), NAMES and
QUIT, with the 001/004/375/372/376 numerics clients wait for on registration,
353/366 on join, and 421 for anything else. One channel per client keeps the
bookkeeping in fixed-size tables; this is a handheld with 8 KiB of task RAM.
MAXCL is 3, bounded by the kernel socket table (listener + client each take a
slot, the rest left for other programs). A fourth client is refused and the
daemon keeps serving - which is how the ~30s close() stall turned up.
Every event is logged to the LCD, so the Game Boy shows its own server
traffic: "+ alice", "alice joined", "<alice> hello".
Verified with real clients over the hub: two and three simultaneous hosts
registering, joining, channel relay (sender not echoed to itself), private
messages by nick, PING/PONG, unknown-command numerics, QUIT propagation, the
overflow refusal, and a second Game Boy running gbos's own irc(1) in the same
channel as a laptop client - messages crossing both ways between them.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A server could only ever hold ONE connection, which makes an ircd pointless:
listen() turned the listener into the connection, and net_find_tcp demuxed on
local port alone, so two clients on 6667 were indistinguishable.
- net_find_tcp now matches an established socket on the full four-tuple
(local port + peer IP + peer port) and only falls back to a LISTENing
socket when none matches.
- A SYN at a listener no longer consumes it: tcp_spawn_conn clones a new
socket (local port + owning pid, so exit/kill still reclaims it) and the
listener keeps listening. accept() returns that socket; closing it leaves
the listener alone. A full table drops the SYN, and the client's retransmit
is taken once a slot frees.
- tcp_peer_is_current preserves BC: net_find_tcp scans with the socket index
there, and the compare needs BC for the rx buffer base. Without this the
scan died at the first mismatching socket, so a second client's handshake
ACK never reached its socket and it hung in SYNRCVD - tcpdump showed our
SYN+ACK going out and the client's NICK retransmitted five times into
silence.
- listen() only conflicts with another LISTENER on the port; connections
share it by design now.
- MAX_SOCKS 4 -> 6 (listener + 3 clients + 2 spare; 537 bytes of WRAM0 still
free), and SK_ACC marks a spawned connection as not yet accepted.
tcp_close's settle is also time-boxed to ~250 ms of real time. It counted
PUMPS (8 x 8000), which is ~30s of wall clock when the peer never answers -
invisible with well-behaved peers, but a daemon refusing a client that stayed
connected froze itself, and every other client with it, for half a minute.
httpd keeps its listener open across requests now, so the next client's SYN
is accepted immediately instead of being dropped between close and re-listen:
three parallel clients go 0.1/1.1/2.1s -> 0.4/1.0/0.1s.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
/# httpd &
host$ curl http://10.0.0.2/ -> a listing of the cart RAM
host$ curl http://10.0.0.2/readme -> the file, from battery-backed SRAM
GET only. "/" serves /index.html if present, else a generated listing; any
directory lists with every entry linked, so you can click around a 1997
handheld's disk from a browser. Content type from the extension. No
Content-Length: HTTP/1.0 + Connection: close delimits the body by closing,
which suits a stack with one in-flight segment.
All output goes through one 200-byte buffer, so each net_send is a full
segment instead of one tiny segment per string.
It drains the whole request, not just the request line. That is not
politeness: leaving the rest unread means those segments are never
acknowledged, so the client retransmits headers for a minute, never sends
its FIN, and its dead connection keeps arriving at port 80 long after we
re-listen. One curl with 1.4 KB of headers stalled every following request
for 30-120s.
It deliberately does NOT read the keyboard either. Backgrounded - the useful
way to run it - every key it polled would be a key stolen from the shell's
prompt. It is a daemon: kill(1) stops it, and the kernel reclaims its socket.
Verified with curl: index, byte-exact file bodies, nested paths, directory
listings, 404, 501, a 1.4 KB request spanning segments, browser user-agents,
three parallel clients (0.1/1.1/2.1s), the shell staying usable alongside it,
and six kill/restart cycles - more than there are sockets.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The stack could only ever dial out. Two new ops give it an ear:
- listen(sock, port): sockets demux on LOCAL PORT alone, so the listener IS
the connection - the SYN fills in the peer and the socket becomes the
connection. One at a time, re-armed by listening again after close, which
is all a 4-socket table can honestly promise. A second client's SYN is
dropped in silence; its retransmit is taken once we are free (~1s), so
three parallel clients complete in 0.1/1.1/2.1s.
- accept(sock): one RX pump per call, never blocks (like recv_nb), so a
server keeps its own main loop and stays killable.
- TCP_LISTEN/TCP_SYNRCVD in tcp_in: answer a SYN with SYN+ACK, become
established on the handshake's ACK, and process any data that ACK carries.
tcp_peer_is_current tells a peer's retransmitted SYN from a newcomer's.
Without it, "any SYN in SYNRCVD is a retransmission" let a second client
*hijack* a connection mid-request: the first client got its SYN+ACK, sent
its GET twice, and was never answered (found in a tcpdump of two parallel
curls).
listen() also refuses a port another socket already holds - two sockets on
one port silently black-hole the second listener, since demux takes the
first match.
Sockets now record the pid that opened them (SK_OWNER) and exit *and* kill
release them: with 4 slots, a few killed irc/nc/httpd runs used to leave the
box unable to open a socket at all until reboot.
tcp_close stops settling the moment the peer's FIN/ACK arrives instead of
burning its whole pump budget. It otherwise sits in FINWAIT for about a
second, and a server that closes and re-listens drops the next client's SYN
in that window: every request after the first paid a SYN retransmit.
1.1s -> 0.1s per request.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The shell has had pipes for a while with nothing to pipe *through*, and
an 18-row screen with no way to stop output scrolling off it.
- grep [-vin] PAT [file]: substring match (no regex), stdin or a file,
grep(1)'s exit convention. The other end of the pipe, at last.
- more [file]: pages 17 lines at a time, --More-- bar in white-on-blue,
any key pages, q quits. Counts *screen* rows, so wrapped lines pay
their real cost. Keys come from pollin/pollcon, not stdin, so
`cat big | more` still has a keyboard.
- cp SRC DST: the fs has rm/mkdir but no way to duplicate a file. mv is
this plus rm (there is no rename). Rejects SRC == DST, which would
otherwise truncate the source via O_WRITE before reading a byte.
- nc [-s] HOST PORT: raw TCP. Interactive (START sends the line with a
CRLF, /q quits) or -s to pump stdin and print the reply with its own
wall-clock idle timeout, because the kernel's blocking recv counts
pumps, not seconds. Generalizes what chat/irc hardcode:
`nc towel.blinkenlights.nl 23` works.
sys_pollcon now pumps the link before draining the console ring. The
ring is filled *by* net_pump, so a program that never touches the
network polled a ring nothing would ever fill: host-injected keys
(gbtype/gbdemo) hung `more` forever, and the Game Boy stopped answering
pings for as long as it ran. KGetc already pumps for this reason.
New tools take banks 35-38. Locals over statics in all four: a program's
_DATA starts at $A000, where the shell leaves the command line.
|
| |
|
|
|
|
|
|
| |
dhcp's recv times out only after ~250*65536 pumps x3 retries, so
fork()+wait() left the screen stuck at 'dhcp: discovering' with no
prompt whenever no DHCP server answered (raw emulator, no bridge).
Fork without waiting: the prompt is immediate, the lease (or 'no
offer') lands a moment later, and the job is reaped by the main loop.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
sys_write sets wBatch; render_cursor_tile - the single choke point all
of term_putc's visible mutations pass through - then marks a dirty
span (per LINE-SLOT, so spans survive scroll rotation for free)
instead of rendering. term_write_end renders every dirty tile exactly
once, then the cursor. Scrolls stay immediate (blank rebuild is cheap
and artifact-free); file-bound writes flush for free.
The enabler is in libc: puts() was a SYS_PUTC trap PER CHARACTER, so
almost no console output ever went through sys_write. It now issues
one SYS_WRITE for the whole string - one trap, one batch, and every
line-printer (echo, irc, the shell) gets the batched path.
Measured: 37-char write = 128k cycles (3.5k/char) vs ~8k/char down
the per-char trap path. Full demo passes; GIF regenerated.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Fano-plane palette scheme: the 7 colors map onto 7 CGB BG palettes so
any color pair shares one palette; a tile's palette is picked from the
set of colors its two cells need (tables generated by tools/gencolor.py).
Per cell a packed (bg<<4)|fg byte lives alongside the char shadow, and
the glyph blitter steers glyph/empty pixels to each cell's fg/bg color
slots via plane masks.
An ANSI-ish CSI parser (ESC [ .. m) drives it; usr/ansi.c demos it and
the irc client now renders hashed nick colors, status dimming and a
channel-activity bar.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The kernel had no clock: TimerISR was a reti stub, IEF_TIMER masked, and
IME was never enabled - the vectors were decorative. sys_sleep just polls
DIV deltas per-process; nothing counted globally.
Now: TAC runs the hardware timer at 16384 Hz with TMA=0, so TIMA overflows
at exactly 64 Hz; TimerISR increments a monotonic 32-bit wTicks (wraps
after ~2.1 years). Scheduling stays cooperative - the ISR is transparent.
Enabling IME in a kernel written for zero interrupts needs care wherever
SP points into memory whose bank is being switched (an IRQ pushes onto SP):
- read_block/write_block map the disk bank over the $A000 window that
holds the caller's stack -> di/ei around the transfer (~2ms, well under
the 15.6ms tick period, so no tick is ever lost)
- hSwitchTo switches SVBK + cart-RAM banks under the outgoing stack ->
di on entry, ei once the incoming stack is mapped
- fork already runs on KSTACK_TOP2 (fixed WRAM) - safe as-is
- term_putc's SVBK switch only remaps $Dxxx, stacks live in $Axxx/$Cxxx
SYS_UPTIME (36) copies the counter (4B LE, di/ei so the read can't tear)
to a user buffer; libc gticks(); usr/uptime.c formats 'up [Nd] H:MM:SS'.
uptime avoids SDCC long div/shift entirely: sm83.lib modules link into
their own areas that land in the $A000 RAM window (latent build.sh trap,
documented there) - bytewise >>6 plus bounded subtraction loops instead.
|
|
|
Userland sources live in usr/ (fits the Unix theme better than 'c').
SDCC output .bin blobs land in build/usr/ with the other build artifacts
instead of littering the source dir; programs.asm INCBINs them from there.
Byte-identical ROM.
|