aboutsummaryrefslogtreecommitdiffstats
path: root/src/syscall.asm (unfollow)
Commit message (Collapse)AuthorFilesLines
3 daysterm+libc: batched write rendering - one tile render per write(), 2.3x linesuser1-1/+3
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.
3 dayskernel: CGB double-speed mode - 2x everything (1.99x measured)user1-3/+6
KEY1 prepare + STOP at KernelInit (skipped on warm reboot if already fast). The PPU/LCD keep their normal rate; the timer and DIV double with the CPU, compensated at the two consumers: - TimerISR now fires at 128 Hz; a toggle byte keeps wTicks at 64 Hz (uptime semantics unchanged) - sys_sleep halves the DIV-delta accumulator's high byte (256 DIV ticks = 1/128 s in double speed) so gsleep/msleep stay real-time Measured: count 60 (scroll-heavy) 3.77s -> 1.89s wall; wTicks 64 Hz over 4s; ping's 800ms msleep pacing unchanged (2.77s for 4 echoes). sl0pboy already emulated KEY1/STOP + per-speed timer/PPU rates.
4 dayskernel: a real timer source (64 Hz tick IRQ) + uptime(1)user1-0/+19
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.
4 daysnet: SYS_POLLCON + larger console ring for link-injected inputuser1-0/+1
Programs that own their main loop (the IRC client) need to poll for typed input without blocking. pollin() only sees the on-screen keyboard; bytes injected over the link (gbhub 'type', for scripted/hub-driven sessions) land in the kernel console ring, previously only drained by the blocking KGetc path. Add SYS_POLLCON: a non-blocking con_pop for userland. Also enlarge that console ring 16 -> 64. One net_pump drains an entire serial burst into the ring at once, so a whole injected command line has to fit or bytes are dropped and lines merge (a 20-char command came out truncated and glued to the next). 64 covers a full line; mask stays a power of two.
4 daysnet: pump the network from the console wait (answer pings at the prompt)user1-31/+7
A Game Boy sitting at the shell prompt now services the network instead of being deaf until you run netd: KGetc (the console input wait) pumps net_pump each poll, so inbound pings are auto-answered while idle. net_pump gained an in-frame model and routes any non-framed link bytes to a small console-input ring (con_push/con_pop), so a headless-injected command still reaches the shell while SLIP frames go to the stack. Removes the old SLIP-skip-in-KGetc hack. Verified: two Game Boys on the hub, GB0 idle at the prompt (no netd), GB1 `ping 10.0.0.2` -> 4/4 replies, routed GB1->hub->GB0->hub->GB1. (Separate, pre-existing: gbhub *spawn* mode and windowed gbjoin can drop an idle link socket - under investigation; daemon mode + headless is solid.)
4 dayskernel: sys_sleep - a cooperative delay off the DIV timer (+ ping pacing)user1-0/+41
There was no time source at all (IRQ vectors just reti; scheduler is purely cooperative). The DIV register (FF04) free-runs at 16384 Hz regardless of interrupts, so sys_sleep accumulates DIV deltas across SchedYields (other procs keep running) until the requested number of 1/64-second units elapse. - SYS_SLEEP(34): B = 1/64s units; libc gsleep(units) / msleep(ms) wrappers. - ping now msleep(800) between echoes, so it paces like real ping instead of blasting all four at once. Verified real-time (capped emulator): replies land ~0.85s apart. In --uncapped runs the delay is GB-time (fast wall-clock), as expected.
4 daysnet: keep network packets out of the console (fixes shell garbage on netboot)user1-0/+23
The link port is both the console-input fallback and the network. At the shell prompt the host's multicast (mDNS/LLMNR/IGMP) was fed to the serial and KGetc read those packet bytes as console input -> garbage in the shell, OSK unusable. Two-part fix: - KGetc now skips SLIP frames (0xC0-delimited) on the serial console path, so inbound packets never surface as console input. Plain injected bytes (the headless tunbridge command path) still pass through. New wConInSkip flag. - netboot/tunbridge only forward IP packets destined to 10.0.0.2 (drop the multicast noise at the bridge). Verified: GB sits at a clean "/#" prompt while all packets (noise included) are forwarded; being-pinged (3/3) and wget still work. On the emulator, the OSK is SELECT=space, START=enter, A=z, B=x, d-pad=WASD/arrows.
4 daysnet: proper socket API in the kernel (SYS_NET) - no more SLIP in userlanduser1-0/+1
The network stack moves into the kernel. src/socket.asm owns SLIP framing, IPv4, RFC1071 checksums, and ICMP; programs now speak a socket API through one syscall (SYS_NET, DE=&netreq dispatched by op): net_socket/connect/send/recv/ close/poll (c/sock.h). No program touches SLIP, IP headers, or checksums. - Socket table (4 sockets) + tx/rx buffers in WRAM0; our IP = 10.0.0.2. - net_pump: drains the link, reassembles SLIP frames, demuxes IPv4. Inbound ICMP echo requests are auto-answered in-kernel, so the GB replies to pings whenever any process pumps RX. - ICMP sockets: send() emits an echo request to the connected peer; recv() returns the matching reply (with a spin/yield timeout). ping.c is now a ~15-line socket client; netd.c is just `for(;;){net_poll(); yield();}`. Verified over tunbridge: /# ping 1.1.1.1 -> replies from the real internet (kernel builds it all) host# ping 10.0.0.2 -> 4/4, 0% loss (kernel auto-answers) Gotchas recorded: gbos.inc isn't a make dep (touch asm after editing); this crt0 doesn't copy initializers (fill arrays at runtime); and the arg string at 0xA000 overlaps _DATA, so parse targets must sit past it (big buffer first). UDP and TCP sockets build on this same core next.
5 daysnet: chat client - async receive + OSK send over the link portuser1-0/+2
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.
5 daysnet: SLIP framing over the link port + a host gateway (echo round-trip)user1-0/+2
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).
5 dayskernel+sh: real anonymous pipes (concurrent, streaming, SIGPIPE)user1-1/+19
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).
5 daysosk: toggle-able on-screen keyboard on the window layeruser1-5/+9
A joypad-driven keyboard that costs zero permanent screen space: it lives on the GB window layer, so SELECT just flips LCDC bit 5 (window enable) and the terminal's background layer underneath is never disturbed. - src/joypad.asm: read $FF00 with edge detection (wPadCur/Prev/New). - src/osk.asm: 3x20 key grid (letters, digits, space, punctuation, plus Enter/Backspace) built once into spare bank-1 VRAM tiles (104+) and laid out on the $9C00 window map, docked to the bottom 3 rows. The highlighted key is a palette swap on its window attribute byte (CGB BG palette 1 = inverted), so moving the cursor is 1-2 attribute writes with no tile rebuilding. SELECT toggles, d-pad moves, A types. - KGetc's console poll loop now polls the joypad and osk_handle each iteration; a key press returns its byte to the reader exactly like a serial byte, so the shell is oblivious to the input source. Verified in the emulator (via --keys): SELECT shows the keyboard, the highlight tracks the d-pad, and typing "ls"+Enter runs the command and lists the files; a second SELECT hides it and reclaims the full 18 rows. Known limitation: the OSK overlays the bottom 3 terminal rows, so if the prompt has scrolled to the very bottom the current input line can be hidden. A follow-up can add a scroll region (terminal uses rows 0-14 while the OSK is up). Input over serial still works unchanged.
5 dayssyscall: mirror console output to the LCD terminaluser1-1/+3
KPutc's console path now renders each byte on the LCD terminal via term_putc in addition to the serial write. Serial is kept so scripted tests still capture output (and for link-cable/debug), while the shell, command output, and echoed input now appear on-screen in the 40-col font with a live cursor and scrolling. Dropped the boot-time term_demo; the terminal starts blank and fills from real console traffic. term_putc now saves/forces/restores SVBK=1 so it always reads and writes the terminal buffer/state in WRAM bank 1, regardless of which process context KPutc is invoked from (defensive; all procs currently use bank 1). The stack lives in non-banked WRAM, so saving SVBK across the switch is safe. Verified: a headless run of `uname; ls; echo hello world` renders each prompt, the echoed command, and its output on the LCD (captured via the new `--headless --shot` path), and serial capture is unchanged.
5 daysfs rewrite stage 3: nested directories, paths, cwd (cd/mkdir/ls)user1-0/+3
- 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.
6 daysfs rewrite stage 1: persistent block device (bounce buffer, cart SRAM)user1-0/+2
- 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.
6 daysjobs: background execution (&), non-blocking reap, spin demouser1-0/+58
- 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].
6 dayskill + ps: implement SYS_KILL (pid 1 immortal) and a ps tooluser1-1/+167
- 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.
6 daysshell: I/O redirection (>/<) and pipes (|); rewrite shell in Cuser1-12/+134
- 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.
6 daysfilesystem: a RAM FS (WRAMX) + ls/cat/save/rm; wc/head read filesuser1-2/+6
- 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.
6 dayswip: sys_read + serial console shell (read-store bug under investigation)user1-1/+20
- 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.
6 dayskernel: complete the process lifecycle (exit/wait/reap + bank free list)user1-11/+139
- 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
6 dayskernel: implement exec()user1-1/+1
- 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
6 dayskernel: implement fork()user1-1/+1
- sys_fork: switch to a kernel stack, bump-allocate a cart-RAM bank, copy the parent's 8 KiB bank via a WRAM bounce buffer, plant a switch-in frame so the child returns 0 to the post-fork PC, fill the child PCB (shared ROM text) - add AllocRamBank (bump allocator) + FindFreeSlot helpers - demo: init forks; parent prints P, child prints C (PCPC...); nested fork gives three distinct pids (123...) - README: document the fork mechanism, mark syscall status