aboutsummaryrefslogtreecommitdiffstats
path: root/docs (follow)
Commit message (Collapse)AuthorAge
* fs: hard links - ln(1), and rm that removes a NAMEuser2026-09-24
| | | | | | | | | | | | | | | | | | gbfs has carried I_NLINK since the beginning and nothing ever incremented it, so a file could only have one name. SYS_LINK adds a second directory entry pointing at the same inode: the data is stored once, and both names read it. That makes rm's job different. It now decrements the link count and only frees the inode and its blocks when the LAST name goes - removing one of two links used to free blocks the other still pointed at. Refused, with the reasons that matter here: linking a directory (it would make a cycle the tree walkers cannot survive), an existing name, and anything under /bin, which is ROM. The syscall takes a request block because the trap needs HL for its dispatch table - and ln copies both paths to the stack first, since that block is a static living at $A000, on top of the command line it is reading.
* fs: mount the builtins at /binuser2026-09-24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The commands only existed in the kernel's program table, so the only way to find out what the machine could do was to already know (or read the source). They are now a directory in the tree: /# ls / /# xxd -l 16 /bin/uname bin 0000: cd 6b 43 06 00 0e 00 f7 .kC..... readme 0008: 18 fe 47 0e 03 f7 c9 21 ..G....! /# ls -l /bin | head -2 - 1 16384 worker /bin is synthetic, not disk: dir_find resolves "bin" in the root to BIN_INO and names inside it through the NameTable, so `ls /bin` lists exactly what the shell can run - one table, one truth, no second list to keep in sync. The root listing carries "bin" as a synthetic first entry so it is discoverable by walking the tree. A program image is inode BIN_FILE|progid: it stats as a 16 KiB file (a program IS its ROM bank) and reads through rom_getb, which maps that bank, takes the byte, and maps the CALLER'S text bank back before returning - the caller is itself executing from $4000, so leaving the wrong bank mapped would return into another program's code. Verified byte-for-byte against the host's view of the blob. The tree is read-only: create/remove/write inside /bin all fail. xxd grows -l (stop after N bytes), because a program image is 2048 dump lines - more than a pipe's temp file can hold, let alone the screen. Two traps worth remembering, both found the hard way here: `and BIN_FILE` destroys A, so the inode must be reloaded before inode_ptr (missing it once made ls -l stat inode 0 and call every file an empty non-directory); and ls now copies its directory argument to the stack first, because stat()'s request block is a static living at $A000 - on top of the command line it was reading.
* kernel: stat/fsstat/isatty syscalls, and O_APPENDuser2026-09-24
| | | | | | | | | | | | | | | | | | | 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 `>>`.
* net: one socket per client - four-tuple demux, spawning accept, bounded closeuser2026-09-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* usr: httpd - the Game Boy serves its filesystem over HTTPuser2026-09-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | /# 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.
* net: three TCP receive bugs - caller corruption, data loss, early EOFuser2026-09-22
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Found by writing nc(1): a 96-byte receive buffer against a host that answers in 200-byte segments hung the process every time. - recv/recv_nb copied RXLEN bytes into the caller's buffer and never looked at the caller's maxlen, so any short buffer got overrun and its stack smashed. Deliver min(RXLEN, maxlen) and keep the tail in RXBUF for the next call. wget only survived because its buffer (220) happens to exceed our MSS. - tcp_buffer_data overwrote RXBUF from offset 0 on every segment, so a segment arriving before the app drained the previous one destroyed unread bytes - silent, undetectable data loss. Append at RXBUF+RXLEN instead, and drop (without advancing rcv_nxt) when it doesn't fit, so the peer retransmits once there's room. RXLEN is one byte, so 255 is the capacity that matters, not RXBUF's 288. - A draining recv now clears RXLEN as well as HASRX: RXLEN is the append offset, and a stale one made every later segment look too big to fit, stalling the connection until the peer gave up. - A FIN past a gap is not end-of-stream. tcp_in accepted any FIN whose segment carried no data, without a sequence check, marking the socket DONE and making recv report EOF at the hole - truncating a transfer right before its last segments. Decide in-order-ness once, before rcv_nxt moves, and re-ACK an out-of-order FIN instead. Verified against a 6300-byte reply received while sending 831 bytes: byte-exact (6339 = payload + banner, 303 lines, first/mid/last intact), plus no regression in ping / nslookup / wget / irc.
* docs: drop the link-drop investigation writeupuser2026-07-18
|
* docs: concise README (networking + demo gif up front); internals.mduser2026-07-18
| | | | | | | | | README is now pitch -> proof (real capture gif) -> quick start -> docs pointers, with the network stack and gbhub/gbjoin/gbtype getting explicit billing. The full technical breakdown (memory model, syscall ABI, fork/exec/context switch, FS layout, toolchain war stories) moves to docs/internals.md, plus previously-undocumented sections on the network stack and the terminal/OSK.
* docs: link-drop investigation resolved - drops are self-healing nowuser2026-07-17
| | | | | | | | | | | | | Rewrite the notes from 'open / not root-caused' to the resolution: the socket never mysteriously dies; every producible drop signature is the emulator process dying silently (SIGPIPE on console echo after a hub restart, terminal death/HUP/stray quit keys in windowed mode - which exactly explains the 'only when idle' correlation - or a guest $ED poweroff), plus observation artifacts in the original session's tooling. Records the fixes shipped on both sides (emulator link auto-reconnect + SIGPIPE ignore + $ED logging; hub eof-vs-error logging, kept stderr, EBUSY guard), the honest spawn-mode caveat, and what's still worth doing.
* docs: investigation notes for the idle link-socket drop (open bug)user2026-07-17