aboutsummaryrefslogtreecommitdiffstats
path: root/src/programs.asm (follow)
Commit message (Collapse)AuthorAgeFilesLines
* kernel: a real timer source (64 Hz tick IRQ) + uptime(1)user4 days1-0/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | 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.
* kernel: dmesg-style boot spew before init spawnsuser4 days1-0/+1
| | | | | | | | | | | | | | | | | | | Print everything the kernel knows implicitly at boot, Linux-flavored, on the LCD console and mirrored over the link (gbhub logs each GB's boot): gbos sm83 microkernel console: CGB (boot a=$11) <- boot ROM's A/B, saved at entry cart: mbc5, 1M rom, 128K sram <- our own cart header ($0147-49) mem: 32K wram 16K vram 127B hram <- CGB constants proc: 8 slots, 31 programs <- link-time table sizes net: slip on link port, 4 sockets fs: gbfs v3 mounted, 120/128 blk free <- bitmap popcount; 'formatted' tty: 40x18 console, SELECT = osk on first boot init: spawning pid 1 New src/dmesg.asm with kernel print helpers (kputc/kputs/kputhex/kputdec) that bypass KPutc (no process context at boot). term_init now runs before net/fs init so the spew is visible as subsystems come up.
* refactor: c/ -> usr/, compiled program blobs out of the source treeuser4 days1-31/+31
| | | | | | | 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.
* irc: a bitchx/irssi-style IRC clientuser4 days1-0/+7
| | | | | | | | | | | | | | | | | | | | | | | irc HOST [NICK] - connects over the kernel TCP stack (DNS-resolves the host), registers, and runs a live client on the 40x18 LCD. UI, within the terminal's means (no cursor addressing - just \r + \b): messages scroll above a fixed irssi-style input line '[#chan] text_' redrawn in place; long input scrolls horizontally. The elders' formats: <nick> msg, <nick:#c> off-channel, *nick* private, -nick- notice, * nick action, >target< outbound, -!- server/status. Keys come from both the OSK (SELECT) and the console ring (pollcon), so a hub can drive it. Commands: /join /part /msg /me /nick /quit /raw, plus bare text to the current channel. Handles PING (PONG + a wink), CTCP ACTION/VERSION, JOIN/PART/QUIT/KICK/NICK, 332/353 topic+names, and 433 nick-in-use (auto-appends _). Registers on bank 32 as program id 30. Note: gbos doesn't zero C statics, so main() inits its state explicitly; the local TCP port is randomized (DIV) to dodge a stale server-side half-open from an unclean prior exit. Tested end to end against a small ircd through gbhub: full MOTD burst, join, channel + private messages, actions, and bot replies all render correctly.
* net: DHCP client - lease the IP at boot instead of hardcoding ituser4 days1-0/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | The address is no longer baked in. net_init starts at 0.0.0.0; a DHCP client runs as the first thing on boot (init/shell forks it and waits), and only once it has a lease (or gives up) does the prompt appear. Kernel: - IP is configurable: 0.0.0.0 until leased; NET_SETIP op stores it. - 16-bit frame length. DHCP/BOOTP packets are ~272 bytes, over the old 255-byte frame cap, so net_slip_send takes a 16-bit length, net_pump assembles into a 320-byte buffer with a 16-bit wNetRxLen, and udp_send writes a 16-bit IP total. Payloads stay <=255 (kept small on purpose) so the per-protocol datalen math is unchanged. wNetTx/wNetRxBuf 256->320, SK_RXBUF 208->288. Userland: - c/dhcp.c: DISCOVER->OFFER->REQUEST->ACK over a UDP socket, then net_setip(); times out gracefully (shell still boots) if there's no server. sh.c runs it before the prompt. Bridge (self-contained DHCP server, no dnsmasq): - tunbridge.py + netboot intercept UDP->:67 and answer OFFER/ACK leasing 10.0.0.2 (gateway 10.0.0.1); everything else is bridged/NATed as before. Regression-tested ICMP/UDP/TCP after the 16-bit change. Verified end to end: dhcp: discovering dhcp: leased 10.0.0.2 /# ping 10.0.0.1 -> 4/4 (traffic from the leased address)
* net: UDP sockets in the kernel + a DNS resolver (real hostname lookups)user5 days1-0/+7
| | | | | | | | | | | | | | | | | | | | Adds UDP to the kernel socket layer on top of the ICMP core: - net_sum() (raw folded sum) split out of net_cksum() so a UDP pseudo-header (src/dst IP + proto + length) can seed the segment checksum. - udp_send: builds IP+UDP with the pseudo-header checksum; NET_BIND sets the local/source port; net_connect sets the peer. - udp_in: demuxes inbound UDP by destination port to the bound socket (net_find_udp), delivers the payload + source addr. Also fixes a real recv bug: net_pump clobbers BC/DE/HL, so the old recv timeout counted in registers and was effectively random. recv now counts in WRAM. New `nslookup HOST` (PROG_NSLOOKUP=28, bank 30): builds a DNS A query and parses the answer (with 0xC0 name-compression) entirely in userland over a UDP socket - the kernel never sees DNS, just UDP. Verified through the bridge NAT: /# nslookup example.com -> example.com -> 172.66.147.243 This gives us name resolution for the TCP/HTTP demo next.
* net: ping - GB-originated ICMP echo (a Game Boy pings the real internet)user5 days1-0/+7
| | | | | | | | | | | | | | | New `ping [A.B.C.D]` program (PROG_PING=27, bank 29): builds and sends ICMP echo requests from 10.0.0.2, then reads replies off the link port. It keeps reading SLIP frames until it finds *our* echo reply, skipping the IGMP/mDNS/ SSDP multicast noise that shares 10.0.0.0/24. Reply wait uses a generous srecv_nb spin budget since the emulator runs uncapped (no timer syscall yet). Verified over the tunbridge (with NAT): /# ping 10.0.0.1 -> 4/4 received, ttl=64 (the SLIP peer/host) /# ping 1.1.1.1 -> 4/4 received, ttl=56 (Cloudflare, real net!) ttl=56 is a real internet round trip (64 minus the hops). Combined with the host being able to ping the GB, the Game Boy is now a full two-way ICMP host.
* net: real IP stack, milestone A - ICMP echo (you can ping a Game Boy)user5 days1-0/+7
| | | | | | | | | | | | | | Start of an actual TCP/IP stack on gbos (TLS stays in a proxy). netd is a userland IP responder over SLIP: our address is 10.0.0.2, the SLIP peer 10.0.0.1. It parses IPv4 headers, answers ICMP echo requests, and rebuilds the packet with correct IP + ICMP checksums (RFC 1071 one's-complement sum, carry-folded - works fine on the SM83). c/netd.c + register; tools/gateway.py gains --mode ping: it crafts ICMP echo requests over SLIP and verifies the replies. Verified: `netd` answers 4 pings, gateway reports reply from 10.0.0.2 with cksum=ok for each. Next: UDP, then TCP.
* net: chat client - async receive + OSK send over the link portuser5 days1-0/+7
| | | | | | | | | | | | | | | | | | | | | | | The application layer of the link-port demo, and it ties the whole system together: the LCD terminal displays, the on-screen keyboard types, and the link port carries a live chat. Kernel: sys_srecv_nb (non-blocking link receive; A=byte, CF=none) and sys_pollin (poll the OSK for a typed char without blocking) - syscalls 31/32. Both are what a poll loop needs to receive and type at once. Userland: c/chat.c runs a poll loop - it feeds non-blocking bytes through a SLIP receive state machine and prints whole incoming frames as messages, while pollin() drives the on-screen keyboard; SELECT shows the keys, type a line, START sends it as a frame. libc srecv_nb()/pollin(). Host: tools/gateway.py --mode chat is a simple bot peer (echoes each GB message and injects a few async ones); --keys can drive the OSK for tests. Verified: the gateway pushes 'welcome', '<alice> hey gameboy!', '<bob> nice link cable' unprompted and the GB displays all three (async receive); typing 'hi' on the OSK echoes it and emits the SLIP frame \xC0hi\xC0 (send). A Game Boy in the chat, keyboard on screen, over the link cable.
* net: wget - real HTTP over the link port via the gatewayuser5 days1-0/+7
| | | | | | | | | | | | | | | | Grow the link-port demo from echo to actual network access. The Game Boy still only does SLIP framing + display; the host gateway does DNS/TCP/HTTP. - c/netlib.h: SLIP framing factored out (header-only, per-program copy). necho.c now uses it too. - c/wget.c: `wget URL` frames the URL, then prints the reply body. The gateway streams the body back as typed frames: 'D'<chunk> ... 'E'. - tools/gateway.py: add --mode http (urlopen the frame as a URL, cap the body, chunk it) alongside --mode echo; --cmd runs any gbos command. Verified: `wget example.com` streams back the full Example Domain HTML onto the terminal; `wget sl0p.foo` fetches the real page. A Game Boy on the web, over the link cable.
* net: SLIP framing over the link port + a host gateway (echo round-trip)user5 days1-0/+7
| | | | | | | | | | | | | | | | | | | | First step of link-port networking. The LCD terminal + OSK freed the serial port from console duty, so it can be the network link. Kernel (src/net.asm): raw link-port serial that bypasses the console/ terminal - sys_ssend (transmit, GB drives the clock) and sys_srecv (receive, GB slave, blocks by yielding). Syscalls 29/30. Userland: libc ssend()/srecv(); c/necho.c does SLIP (RFC 1055) framing over them - send a packet, receive the reply, print it. Host: tools/gateway.py wraps the emulator, owns its link serial, speaks SLIP, and (for now) echoes every frame back - the "link cable adapter". Console (ASCII) bytes on the same channel are printed for visibility. Verified: `necho` sends a SLIP frame, the gateway decodes+echoes it, and gbos prints the reply - a real framed round-trip over the Game Boy link port. Next: swap the echo for actual network ops (DNS/HTTP or IRC/chat).
* kernel+sh: real anonymous pipes (concurrent, streaming, SIGPIPE)user5 days1-0/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Replace the shell's temp-file pipe hack with proper in-kernel FIFOs. Kernel (src/pipe.asm, new): - A small pool of bounded ring buffers (PIPE_MAX=2, 64 B each) with writers/readers refcounts. Pipe fds are $F0+idx*2 (read) / +1 (write); $FF stays "console". - SYS_PIPE allocates one (writers=readers=1) and returns the read fd (write = read+1). getb/putb/close dispatch pipe fds here; sys_exit drops the refcounts held as PROC_STDIN/PROC_STDOUT. - Blocking with SchedYield, which is the flow control: read blocks while empty with a writer (EOF once writers hit 0), write blocks while full with a reader, and if the last reader is gone the writer is killed (SIGPIPE -> exit 141). Cooperative-scheduler friendly. Shell (c/sh.c): - run_pipeline(): split on '|', make a pipe between adjacent stages, and fork ALL stages concurrently (no wait between), wiring stdin/stdout; then wait for all. Per-stage >/< still honored; orphaned pipe ends are closed on a lookup miss so EOF/EPIPE propagate. - Drop the __pipe temp file and its 2 KB / serialized limits. libc: pipe(). New c/ptest.c exercises the FIFO (write, read back, EOF). Now works (old version couldn't): multi-stage a|b|c; streaming beyond 2 KB (count 120 | wc = 3372 bytes through a 64 B buffer); early-exit SIGPIPE (count 200 | true kills count instead of hanging/overflowing a file).
* term/osk: scroll region so the OSK never hides the input lineuser5 days1-0/+7
| | | | | | | | | | | | | | | | | | | The terminal now has a variable usable height (wTermRows). term_scroll and cursor_down operate within [0 .. wTermRows-1], and term_set_rows(n) shrinks or grows that region, scrolling the cursor up into view when it would fall outside. osk_toggle shrinks the terminal to the rows above the keyboard on show (18 - OSK_ROWS = 15) and restores full height on hide, so the active line is always visible just above the docked keyboard, and hiding the OSK reclaims all 18 rows. Also add c/count.c ("count [n]", default 25): prints n numbered lines to observe/debug scrolling and the scroll-region behavior. Registered as program id 21 / bank 23. Fixed the arg to read argv[0] (getargs returns the string after the command name). Verified: filling the screen then opening the OSK scrolls the latest line to row 14 (visible) with the keyboard at 15-17; closing it restores rows 15-17; count N prints exactly N lines.
* fs rewrite stage 3: nested directories, paths, cwd (cd/mkdir/ls)user6 days1-0/+7
| | | | | | | | | | | | | | | | - directories generalized: dir_find/dir_add/dir_remove take any dir inode; every dir carries '.'/'..'. Path resolution (resolve / resolve_parent) walks absolute or cwd-relative paths; open/mkdir/chdir/remove/opendir take paths. - per-process cwd: PROC_CWD in the PCB (root by default, inherited on fork); sys_chdir sets it, sys_opendir points ls at any directory. - shell: 'cd' builtin + cwd-aware prompt; 'mkdir'/'ls <dir>' tools; 'exit'/'quit' and EOF call poweroff() (clean shutdown via the $ED opcode -> reliable save). - three HL/buffer-clobber bugs fixed along the way: resolve didn't preserve the path cursor across dir_find; cur_cwd clobbered HL; resolve_parent set the final name before resolve() overwrote wFsNameBuf (now copied after). - verified: nested mkdir/cd/save, multi-level paths (cat docs/sub/b), and the whole tree persists across a reboot. - README: document directories + the poweroff opcode.
* fs rewrite stage 1: persistent block device (bounce buffer, cart SRAM)user6 days1-0/+7
| | | | | | | | | | | | | | - blk.asm: read_block/write_block move 256-byte blocks between battery-backed cart-RAM banks (8-11, the 'disk') and a WRAM bounce buffer. Banking is hidden in those two routines; they inline the 'restore my bank' step so they never touch the stack while the disk bank is mapped over the process's $A000 window (the stack lives there too -- a call/ret would rug-pull it). - format-on-first-boot: superblock magic in block 0; otherwise the disk persists. - cart RAM bumped to 128 KiB (-r 4); process banks 0-7, disk banks 8-11. - debug tool 'blk fill/peek' + syscalls to validate round-trip and persistence. - verified: round-trip incl. cross-bank (block 100 -> bank 11); block survives a reboot via .sav; magic intact (no reformat on 2nd boot). - old WRAM 8-slot FS still backs the tools until stages 2-3.
* jobs: background execution (&), non-blocking reap, spin demouser6 days1-0/+7
| | | | | | | | | | | - sys_reap: nohang reap of a finished zombie child (-> pid or 0). - shell: 'cmd &' launches in the background (prints [pid], no wait); reaps finished jobs at the prompt ([pid done]). Foreground now waits for its own child specifically (loops wait(), reporting bg completions meanwhile) so kill reports the right pid. - spin: a process that yields forever - a target for ps/kill. - verified: spin &; ps shows it; kill <pid> removes just that one; immortal init; self-finishing bg worker reaped with [pid done].
* kill + ps: implement SYS_KILL (pid 1 immortal) and a ps tooluser6 days1-0/+16
| | | | | | | | | | | - PCB gains PROC_PROG (running program id), set by exec, inherited on fork. - sys_kill(pid): reject pid 1 (immortal) and unknown/dead pids; mark the target zombie, reparent its children to init, wake its parent if blocked in wait; schedule away if a process kills itself. Reuses FindPcbByPid/ReparentToInit. - sys_ps(slot,buf)->{pid,state,prog}; sys_progname(id) via NameTable reverse lookup. NameTable gains sh/ps/kill. - libc: kill/psget/progname; tools ps (pid state cmd) and kill <pid>. - verified: ps lists sh(B)+ps(R); kill 1 refused; kill 42 fails.
* shell: I/O redirection (>/<) and pipes (|); rewrite shell in Cuser6 days1-140/+41
| | | | | | | | | | | | | - kernel I/O routing: PCB gains PROC_STDIN/PROC_STDOUT (default console $FF), inherited across fork. KGetc/KPutc route read/write/putc to the console or a file fd. New syscalls: putc(16), setin(17), setout(18), lookup(19). read/write now go through the routing; putc/puts/nl use SYS_PUTC. - sys_lookup + NameTable move command-name resolution into the kernel. - shell rewritten in C (c/sh.c): tokenizes a line, parses > / < / |, and drives fork+setin/setout+exec+wait. Pipes run as 'a > __pipe ; b < __pipe' (temp file). asm shell + cmdtab removed; StrEqual/SkipName kept for sys_lookup. - libc: fork/exec/wait/setin/setout/lookup wrappers. - verified: echo>file, cat file, wc<file, cat readme|wc -l, echo ...|wc.
* filesystem: a RAM FS (WRAMX) + ls/cat/save/rm; wc/head read filesuser6 days1-0/+21
| | | | | | | | | | | - fs.asm: 8-slot RAM filesystem in WRAMX ($D000-$DFFF), name[8]+len[2]+data[502]; syscalls open/close/getb/putb/list/remove, seeded with a 'readme' at boot. - libc: open/close/fgetc/fputc/flist/fremove wrappers + O_READ/O_WRITE/NOFD. - tools: ls, save (stdin->file, one line), rm; cat/wc/head now take a file arg. - fix: syscall dispatch clobbers A, so putb takes its byte in E (was reading the handler's low address byte, 0xDC); ls NUL-terminates 8-char names. - verified: save/cat/ls/rm cycle; wc readme -> '2 7 38'; head -n 1 readme. - README: document the filesystem + the A-clobber ABI note.
* tools: wc, head, and an argc/argv demo (args) + libc.c helpersuser6 days1-0/+21
| | | | | | | | | | - c/libc.c: shared C helpers linked into every program - putu (print decimal), atou (parse decimal), argv_parse (tokenize getargs() into argc/argv). - wc: count lines/words/chars of stdin. head [n]: first n lines (drains rest to EOF). args: argc/argv demo. - pid now uses libc putu; build.sh links libc.c; Makefile CBLOBS + libc dep. - README: document the new tools + argv_parse. - verified: 'args one two three' -> argc=3/argv[..]; wc '2 3 16'; head 2.
* libc + CLI tools: a small C userlanduser6 days1-3/+68
| | | | | | | | | | | - libc: add putc, puts, strlen, getargs (args string), EOF (=4) for readc. - args: shell splits the command line at the first space ("cmd\0args\0" at $A000, inherited by the child via fork); getargs() returns the arg string. - shell: exit on Ctrl-D/EOF; $ prompt over a clean read loop. - tools (c/): echo (argv), cat (stdin->stdout to EOF), uname, pid (decimal print), true, false. Each built to a ROM-bank blob and registered. - Makefile: CBLOBS list builds all C programs. - README: document libc + the tool set.
* C toolchain: run SDCC-compiled C programs as gbos processesuser6 days1-0/+13
| | | | | | | | | | | | | - c/{gbos.h,crt0.s,libc.s,build.sh}: SDCC sm83 -> ROM-bank blob toolchain. libc syscall wrappers save/restore BC/DE/HL around rst $30 (trap clobbers them; SDCC expects them preserved). - build via SDCC native asxxxx path (sdasgb/sdldgb), crt0 linked first so _start is the $4000 entry; blob INCBIN'd into a ROM bank. - chello.c: prints a message + getpid() -> runs as 'chello' shell command. - Makefile: auto-build c/*.bin, track as a dep of programs.o; gitignore blobs. - README: document the C toolchain + the SDCC --asm=rgbds codegen bug that forced the native-toolchain approach. - verified: '$ chello' -> 'hello from C on gbos!' / 'my pid is 2'.
* shell working: fix syscall ABI (trap clobbers HL) + documentuser6 days1-1/+5
| | | | | | | | | | | - root cause of the read/echo corruption: SyscallTrap uses HL to index the dispatch table, so HL is clobbered across a syscall; the shell then wrote its buffer pointer's garbage into the $0000-$7FFF MBC register region, corrupting the mapper. fix: userland saves HL around sys_read (push/pop). - shell now runs commands end to end: $ hello -> hello world!, $ worker -> worker!, $ foo -> ? - README: document that syscalls clobber BC/DE/HL (return in A, B for wait); add read + shell to the syscall table.
* wip: sys_read + serial console shell (read-store bug under investigation)user6 days1-13/+153
| | | | | | | | | | | | - sys_read (SYS_READ): blocking console input via external-clock serial poll; yields while waiting. Verified working (direct-echo cat: 'hi' -> 'hi'). - shell (PROG_SH): prompt/read/parse/fork+exec+wait; worker+hello programs; StrEqual/SkipName helpers; command table. - init now execs the shell. - KNOWN BUG: storing the typed char to the line buffer at $A000 (cart RAM of an exec'd program) corrupts the following sys_read; sys_write reading $A000 also returns $3E. Kernel syscalls themselves are correct; isolating the cart-RAM / exec RAM-bank interaction next.
* kernel: complete the process lifecycle (exit/wait/reap + bank free list)user6 days1-35/+19
| | | | | | | | | | | | | | - exit(): zombie + status, reparent orphans to init, wake blocked parent, schedule away forever (resources reclaimed by the reaper) - wait(): scan for a zombie child; reap (free cart-RAM bank + PCB slot) and return pid/code; block PS_BLOCKED + yield if children still live; $FF if none - cart-RAM banks are now a free list (wBankUsed bitmap): AllocRamBank/FreeRamBank - scheduler: FindNextReady returns carry when nothing runnable; yield idles instead of blindly picking slot 0 - helpers: FindPcbByPid, ReparentToInit - demo: init forks 3 workers -> exec -> exit(pid) -> wait/reap => wwwR2R3R4! verified bank recycling with 6 workers over 3 banks (w2w3w4w5w6w7) - README: document the lifecycle + syscall status
* kernel: implement exec()user6 days1-0/+52
- sys_exec(B=program id): look up ProgramTable, point PROC_ROMB at the program's ROM text bank, map it into $4000-$7FFF, reset stack, jp to entry (no return) - programs.asm: two demo programs (ping/pong) ORG'd at the SAME $4000 in banks 2 and 3 - proves execution is driven purely by PROC_ROMB - demo: init forks; parent execs PROG_PING (bank2, '1'), child execs PROG_PONG (bank3, '2') -> '1212...', 2000/2000 balanced, zero garbage - README: document exec + text-in-ROM model