]> Projects (at) Tadryanom (dot) Me - AdrOS.git/log
AdrOS.git
3 weeks agofeat: POSIX Phases 2-6 — syscalls, ulibc functions, headers, malloc, Newlib prep
Tulio A M Mendes [Sat, 14 Mar 2026 00:47:53 +0000 (21:47 -0300)]
feat: POSIX Phases 2-6 — syscalls, ulibc functions, headers, malloc, Newlib prep

Phase 2 — Critical Kernel Syscalls:
- SYSCALL_GETRLIMIT (129) / SYSCALL_SETRLIMIT (130): per-process resource
  limits (RLIMIT_NOFILE, RLIMIT_STACK, RLIMIT_CORE, etc.) stored in process
  struct, inherited on fork, enforced by setrlimit with root privilege check
- SYSCALL_SETSOCKOPT (131) / SYSCALL_GETSOCKOPT (132): SO_REUSEADDR,
  SO_KEEPALIVE, SO_RCVBUF/SNDBUF, SO_ERROR, SO_TYPE via lwIP
- SYSCALL_SHUTDOWN (133): TCP half-close via tcp_shutdown
- SYSCALL_GETPEERNAME (134) / SYSCALL_GETSOCKNAME (135): socket address query
- Added missing errno codes: ENOPROTOOPT, EOVERFLOW, ELOOP

Phase 3 — Critical ulibc Functions (12 new source files):
- setjmp/longjmp/_setjmp/_longjmp/sigsetjmp/siglongjmp (i386 assembly)
- sleep()/usleep() wrappers over nanosleep
- execvp()/execlp()/execl() with PATH search
- getopt()/getopt_long() full POSIX implementation
- strerror()/perror() with 35-entry error string table
- strtoul()/strtoll()/strtoull() with base auto-detection
- setenv()/unsetenv()/putenv() with owned environ array
- signal() wrapper over sigaction (SA_RESTART)
- abort() sends SIGABRT, atexit() with 32-handler registry
- exit() now calls atexit handlers in reverse order
- rand()/srand() LCG PRNG (RAND_MAX=0x7FFF)
- strtok_r(), strnlen(), strspn(), strcspn(), strpbrk()

Phase 4 — Critical Headers (9 new headers):
- setjmp.h: jmp_buf, sigjmp_buf for i386
- locale.h: LC_* constants, struct lconv, setlocale/localeconv stubs
- pwd.h/grp.h: struct passwd/group, getpwnam/getpwuid/getpwent stubs
- getopt.h: struct option, getopt_long declarations
- fnmatch.h + fnmatch(): glob-style pattern matching with FNM_PATHNAME
- sys/select.h: fd_set, FD_SET/CLR/ISSET/ZERO macros, select()
- sys/resource.h: struct rlimit, RLIMIT_* constants, getrlimit/setrlimit

Phase 5 — Proper malloc with free():
- Replaced bump allocator with address-ordered free-list allocator
- 8-byte aligned blocks with 8-byte header (size | used_bit, next_free)
- First-fit allocation with block splitting
- free() with address-ordered insertion and bidirectional coalescing
- realloc() now preserves old data size from block header

Phase 6 — Newlib Port (from previous commit):
- libgloss stubs already complete, syscall numbers updated

97/97 smoke tests pass, cppcheck clean.

3 weeks agofeat: gettimeofday + mprotect syscalls + Newlib libgloss port
Tulio A M Mendes [Sat, 14 Mar 2026 00:18:08 +0000 (21:18 -0300)]
feat: gettimeofday + mprotect syscalls + Newlib libgloss port

Kernel:
- Add SYSCALL_GETTIMEOFDAY (127): returns RTC epoch seconds + TSC-derived
  microseconds via struct timeval. Timezone arg ignored per POSIX.
- Add SYSCALL_MPROTECT (128): changes page protection on heap, mmap, and
  stack regions. Converts POSIX PROT_READ/WRITE/EXEC to VMM flags and
  calls vmm_protect_range(). Validates ownership before modifying PTEs.

ulibc:
- Add gettimeofday() wrapper in sys/time.h + time.c
- Add mprotect() wrapper in sys/mman.h + mman.c
- Add SYS_GETTIMEOFDAY/SYS_MPROTECT to ulibc syscall.h

Newlib port (newlib/):
- newlib/libgloss/adros/crt0.S: C runtime startup for AdrOS, calls
  __libc_init_array/__libc_fini_array for Newlib/C++ ctor/dtor support
- newlib/libgloss/adros/syscalls.c: all 21 Newlib-required OS stubs
  (_exit, _read, _write, _open, _close, _lseek, _fstat, _stat, _isatty,
  _kill, _getpid, _sbrk, _link, _unlink, _fork, _execve, _wait, _times,
  _gettimeofday, _rename, _mkdir) implemented via INT 0x80
- newlib/libgloss/adros/Makefile: builds crt0.o + libadros.a
- newlib/README.md: build instructions for full Newlib cross-compilation
- newlib/patches/README.md: documents config.sub, configure.host, and
  libgloss/configure.in changes needed in Newlib source tree

All 21 Newlib libgloss stubs are now implemented. To build Newlib:
  1. Copy libgloss/adros/ into Newlib source tree
  2. Add i686-*-adros* target to configure files
  3. Build with: ../configure --target=i686-adros && make

3 weeks agodocs: full POSIX/Unix audit + fix git-clone breakage with submodules
Tulio A M Mendes [Sat, 14 Mar 2026 00:02:44 +0000 (21:02 -0300)]
docs: full POSIX/Unix audit + fix git-clone breakage with submodules

- Add docs/FULL_POSIX_AUDIT.md: comprehensive analysis of POSIX gaps,
  build system issues, and porting requirements for Newlib/GCC/Binutils/
  Bash/Busybox
- Convert lwIP and doomgeneric from untracked nested repos to proper
  git submodules (.gitmodules) so git clone --recursive works
- Update .gitignore: remove third_party/ and user/doom/doomgeneric
  exclusions now that they are tracked as submodules

Build system was broken after git clone: lwIP (required) and doomgeneric
(optional) directories were empty because they were gitignored nested
repos with no .gitmodules. Now users can:
  git clone --recursive <url>
  make CROSS=1 && make iso

3 weeks agofix: build system cleanup — replace non-freestanding <string.h> with utils.h, paramet...
Tulio A M Mendes [Fri, 13 Mar 2026 23:45:00 +0000 (20:45 -0300)]
fix: build system cleanup — replace non-freestanding <string.h> with utils.h, parameterize toolchain, fix header conflicts

- Kernel: replace #include <string.h> (not freestanding) with #include "utils.h"
  in src/arch/x86/fpu.c, src/kernel/fpu.c, src/rump/rumpuser_adros.c
- Makefile: define USER_CC/USER_LD/USER_AR variables for userspace cross-compilation,
  replacing all hardcoded i686-elf-gcc/ld references in FULLTEST, DYN_CC, DYN_LD,
  LDSO, PIE_SO, PIE_ELF rules
- Makefile: pass USER_CC/USER_LD/USER_AR to ulibc sub-make for consistent toolchain
- Makefile: add missing .PHONY targets (test-battery, run-arm, run-riscv, run-mips)
- ulibc Makefile: use ?= for CC/AS/AR/LD so parent can override
- doom Makefile: use ?= for CC/LD so parent can override
- ulibc: remove duplicate stat/fstat declarations from unistd.h (conflicted with
  sys/stat.h when both included); proper declarations remain in sys/stat.h per POSIX

6 weeks agofeat: /dev/hdX block device nodes and /proc/dmesg
Tulio A M Mendes [Tue, 17 Feb 2026 08:01:55 +0000 (05:01 -0300)]
feat: /dev/hdX block device nodes and /proc/dmesg

1. ATA block devices in devfs:
   Detected ATA drives (hda, hdb, hdc, hdd) are now registered as
   block device nodes in /dev via ata_register_devfs(). Each node
   supports read/write at byte offsets (with sector-aligned I/O
   internally). Previously, drives were detected but invisible
   in /dev, so 'ls /dev' showed no disk devices and mount had to
   use ata_name_to_drive() internally.

2. /proc/dmesg:
   Added /proc/dmesg to procfs that reads the kprintf ring buffer
   via klog_read(). Uses heap allocation (16KB) to avoid kernel
   stack overflow. The 'dmesg' command now works correctly.

6 weeks agofeat: ALT key support in PS/2 keyboard driver
Tulio A M Mendes [Tue, 17 Feb 2026 07:51:33 +0000 (04:51 -0300)]
feat: ALT key support in PS/2 keyboard driver

Added alt_held state tracking for Left ALT (scancode 0x38/0xB8)
and Right ALT (E0 0x38/0xB8). When ALT is held, the driver
emits ESC (0x1B) prefix before the character, which is the
standard terminal encoding for Alt+key combinations.

Also handles Right CTRL (E0 0x1D/0x9D) which was previously
ignored since it uses E0-prefixed scancodes.

Summary of modifier keys now supported:
  - Shift (L/R): uppercase and symbol characters
  - CTRL (L/R):  control characters (c & 0x1F)
  - ALT (L/R):   ESC prefix + character

6 weeks agofix: mount command -t option argument parsing
Tulio A M Mendes [Tue, 17 Feb 2026 07:47:48 +0000 (04:47 -0300)]
fix: mount command -t option argument parsing

The mount command parsed device=argv[1] and mountpoint=argv[2]
BEFORE scanning for -t, so 'mount -t fat /dev/hdb /test' set
device='-t' and mountpoint='fat' instead of the correct values.

Rewrote argument parsing to scan for -t first, then collect
remaining positional arguments as device and mountpoint.
Added usage message for missing arguments.

6 weeks agofix: keyboard CTRL key support for CTRL+C/CTRL+Z in shell
Tulio A M Mendes [Tue, 17 Feb 2026 07:45:06 +0000 (04:45 -0300)]
fix: keyboard CTRL key support for CTRL+C/CTRL+Z in shell

Root cause: The PS/2 keyboard driver had no CTRL key tracking.
When CTRL was held and a letter pressed, the driver sent the
plain letter character (e.g., 'c') instead of the control
character (e.g., 0x03 for CTRL+C).

Fix: Added ctrl_held state tracking in hal/x86/keyboard.c for
Left CTRL (scancode 0x1D press / 0x9D release). When CTRL is
held and a letter key is pressed, emit (c & 0x1F) — the
standard control character encoding:
  CTRL+C = 0x03, CTRL+Z = 0x1A, CTRL+D = 0x04, etc.

Shell read_line_edit() already handled these bytes correctly:
  - 0x03 (CTRL+C): prints ^C, newline, cancels current line
  - 0x1A (CTRL+Z): ignored at prompt (no foreground job)
  - 0x04 (CTRL+D): EOF on empty line

During command execution, tty_restore() re-enables ISIG, so
the TTY kernel driver intercepts CTRL+C/CTRL+Z and sends
SIGINT/SIGTSTP to the foreground process group via TIOCSPGRP.

6 weeks agofix: CTRL+C/CTRL+Z job control and doom build errors
Tulio A M Mendes [Tue, 17 Feb 2026 07:30:53 +0000 (04:30 -0300)]
fix: CTRL+C/CTRL+Z job control and doom build errors

1. CTRL+C/CTRL+Z: Shell now calls setsid() instead of setpgid(0,0)
   to create a proper session. This initializes tty_session_id so
   TIOCSPGRP can actually set child processes as the foreground
   group. Previously, TIOCSPGRP silently returned -EPERM because
   tty_session_id was 0.

2. Doom mkdir: Added mkdir/stat/fstat/chmod declarations to
   user/ulibc/include/sys/stat.h where POSIX expects them.
   Doom's m_misc.c includes sys/stat.h for mkdir().

3. Doom __divdi3: Added libgcc.a to doom link step to provide
   compiler runtime helpers for 64-bit arithmetic on i386.

6 weeks agofeat: shell job control (&, &&, ||) and CTRL+C/CTRL+Z support
Tulio A M Mendes [Tue, 17 Feb 2026 07:15:18 +0000 (04:15 -0300)]
feat: shell job control (&, &&, ||) and CTRL+C/CTRL+Z support

1. Background processes (&): trailing & forks without waiting, prints
   [bg] PID. Works for simple commands and pipelines.

2. Command chaining (&&): executes next command only if previous
   succeeded (exit status 0). Skips remaining && chain on failure
   until a || or ; is found.

3. OR chaining (||): executes next command only if previous failed
   (exit status != 0). Skips remaining || chain on success until
   a && or ; is found.

4. CTRL+C / CTRL+Z: shell ignores SIGINT/SIGTSTP/SIGQUIT. Child
   processes get their own process group (setpgid) and are set as
   the foreground group (TIOCSPGRP). CTRL+C sends SIGINT only to
   the child, not the shell. After child exits, shell restores
   itself as foreground group.

New files:
- user/ulibc/include/sys/wait.h: WIFEXITED/WIFSIGNALED/etc macros

Modified:
- user/sh.c: process_line rewritten for ;/&&/||/& operators,
  run_simple and run_pipeline use setpgid+TIOCSPGRP job control
- user/ulibc/include/termios.h: added TIOCSPGRP/TIOCGPGRP

6 weeks agofix: init PID 1, ls -l permissions/size, doom dynamic linking
Tulio A M Mendes [Tue, 17 Feb 2026 07:01:05 +0000 (04:01 -0300)]
fix: init PID 1, ls -l permissions/size, doom dynamic linking

1. Init process now gets PID 1 (like Linux): next_pid starts at 2,
   sched_assign_pid1() explicitly assigns PID 1 to the init process
   after it loads. Kernel threads and AP idles get PIDs 2+.

2. ls -l now shows permissions, nlink, size via stat() on each entry
   instead of just the type character.

3. doom.elf Makefile switched from static linking (libulibc.a) to
   dynamic linking (libc.so via ld.so) like all other user commands.

6 weeks agofix: diskfs kfree-on-static-root, mount syscall, user addr space 8MiB->1GiB
Tulio A M Mendes [Tue, 17 Feb 2026 06:50:42 +0000 (03:50 -0300)]
fix: diskfs kfree-on-static-root, mount syscall, user addr space 8MiB->1GiB

Bug 1: ls /disk heap corruption — diskfs_close_impl called kfree on
static g_root BSS variable. Added guard: skip kfree when node == g_root.

Bug 2: mount command only displayed mounts. Added SYSCALL_MOUNT (126)
with support for tmpfs and disk-based filesystems (diskfs/fat/ext2/persistfs).
Updated userspace mount to call the syscall with device, mountpoint, and
-t fstype args.

Bug 3: doom 'Unable to allocate 5 MiB' — user address space was capped
at 8 MiB (USER_STACK_BASE=0x00800000). Raised to 1 GiB (0x40000000) in
elf.c, usermode.c, and syscall_brk_impl.

6 weeks agotest: add host tests for sed, awk, who, find, which (64/64 pass, 2 skip)
Tulio A M Mendes [Tue, 17 Feb 2026 06:27:57 +0000 (03:27 -0300)]
test: add host tests for sed, awk, who, find, which (64/64 pass, 2 skip)

6 weeks agofix: UAF in alarm queue on reap, FD leak on self-SIGKILL and posix_spawn execve failure
Tulio A M Mendes [Tue, 17 Feb 2026 06:26:48 +0000 (03:26 -0300)]
fix: UAF in alarm queue on reap, FD leak on self-SIGKILL and posix_spawn execve failure

6 weeks agofeat: add sed, awk, who, top, du, find, which commands + shell heredoc support
Tulio A M Mendes [Tue, 17 Feb 2026 06:20:25 +0000 (03:20 -0300)]
feat: add sed, awk, who, top, du, find, which commands + shell heredoc support

6 weeks agofix: ps shows [kernel] for empty cmdline, ls sorts alphabetically, add qsort to ulibc
Tulio A M Mendes [Tue, 17 Feb 2026 06:13:11 +0000 (03:13 -0300)]
fix: ps shows [kernel] for empty cmdline, ls sorts alphabetically, add qsort to ulibc

6 weeks agofix: create common mountpoint directories under root
Tulio A M Mendes [Tue, 17 Feb 2026 06:00:09 +0000 (03:00 -0300)]
fix: create common mountpoint directories under root

6 weeks agofix: handle ANSI clear/home sequences on VGA console
Tulio A M Mendes [Tue, 17 Feb 2026 05:56:08 +0000 (02:56 -0300)]
fix: handle ANSI clear/home sequences on VGA console

6 weeks agotest: expand test suite — 97 smoke tests, 56 host utility tests
Tulio A M Mendes [Tue, 17 Feb 2026 05:34:53 +0000 (02:34 -0300)]
test: expand test suite — 97 smoke tests, 56 host utility tests

- Add 8 new kernel tests to fulltest.c:
  /proc/PID/cmdline, /proc/PID/status, /dev/console, multi-pty,
  dup standalone, pipe EOF, readdir /proc, readdir /bin

- Create tests/test_host_utils.sh: host-compilable test harness for
  20 userspace utilities (echo, cat, head, tail, wc, sort, uniq, cut,
  grep, tr, basename, dirname, tee, dd, pwd, uname, id, printenv,
  cp, mv, touch, rm, mkdir, rmdir, ln) — 56 tests

- Fix echo: leading space when flags shift arg index
- Fix tail: off-by-one with trailing newline
- Fix tee/touch/cp/mv/dd: missing mode arg on open(O_CREAT)
- Fix ulibc open(): make variadic to accept optional mode

- Update smoke_test.exp with 8 new patterns (97 total)
- Add host utility tests to Makefile test-host target

Tests: 97/97 smoke, 28+19+56=103 host, cppcheck clean

6 weeks agofix: shell/command bugs, new utilities, procfs race condition
Tulio A M Mendes [Tue, 17 Feb 2026 04:31:30 +0000 (01:31 -0300)]
fix: shell/command bugs, new utilities, procfs race condition

Shell fixes:
- Fix DELETE key showing ~ (handle \x1b[3~ escape sequence + Home/End)
- Fix builtin redirections (echo > file now works via saved fd restore)
- Fix initrd readdir (root cause of ls /bin empty + tab completion broken)

Command fixes:
- Fix cut -dX/-fN combined argument parsing (POSIX style)
- Fix ps showing ? for PIDs: add cmdline[128] to process struct, populate in execve + init
- Fix procfs race condition: use sched_lock for process list traversal
- Make sched_lock non-static for procfs access

New commands (22 total):
- Previous session: mount, umount, env, kill, sleep, clear, ps, df, free, tee, basename, dirname, rmdir
- This session: grep, id, uname, dmesg, printenv, tr, dd, pwd, stat

Arch contamination note: vdso.c includes arch/x86/kernel_va_map.h directly (acceptable for now, only x86 target)

Tests: 89/89 smoke, cppcheck clean

6 weeks agouserspace: major refactoring — dynamic linking, new commands, SysV init
Tulio A M Mendes [Tue, 17 Feb 2026 02:22:03 +0000 (23:22 -0300)]
userspace: major refactoring — dynamic linking, new commands, SysV init

Infrastructure:
- Rename user/init.c → user/fulltest.c, move to /sbin/fulltest
- Remove .elf extensions from all InitRD binaries
- Build ulibc as shared library (libc.so) with PIC objects
- Fix crt0.S to correctly parse argc/argv/envp from execve stack
- Fix ld.so: restore stack pointer before jumping to program entry
- Fix ld.so: add R_386_GLOB_DAT/R_386_COPY eager relocation processing
- Fix ld.so: move find_shlib_info() before relocation processing
- Create dyn_linker.ld for dynamically-linked PIE executables
- Add missing ulibc functions: waitpid, getdents, stat, fstat, chmod,
  chown, link, symlink, readlink, sigaction
- Add dirent.h header, expand sys/stat.h with permission macros
- Update Makefile: dynamic linking build rules for all user commands

Rewritten commands (now use ulibc + dynamic linking via ld.so):
- cat: proper POSIX with stdin/- support, 4KB buffer
- echo: -n, -e, -E flags, escape sequence handling
- ls: -a, -l flags, getdents-based directory listing
- mkdir: -p flag for recursive parent creation
- rm: -r, -f, -d flags

New commands (all dynamically linked):
- File management: cp, mv, touch, ln (-s for symlinks)
- Text processing: head (-n), tail (-n), wc (-l/-w/-c),
  sort (-r/-n), uniq (-c/-d), cut (-d/-f)
- Permissions: chmod (octal), chown (owner:group), chgrp
- System info: date, hostname, uptime

Shell improvements (/bin/sh):
- Variable assignment (VAR=value) and expansion ($VAR, ${VAR}, $?)
- Environment variables (export VAR=value)
- Line editing: left/right arrows, Ctrl+A/E/U, backspace
- Command history: up/down arrows, dedup
- Builtins: cd, pwd, export, unset, set, type, exit, echo
- PATH-based command resolution with colon-separated dirs
- Quote handling (single and double quotes)
- Semicolon command separation
- Append redirection (>>)

SysV init (/sbin/init):
- Parses /etc/inittab (id:runlevels:action:process format)
- Actions: sysinit, wait, once, respawn, ctrlaltdel, shutdown
- Runlevel support (0-6, S)
- Default behavior without inittab: run rcS then respawn /bin/sh
- Child reaping and respawn loop

Kernel changes:
- Default init path changed to /sbin/init (was /bin/init.elf)
- grub.cfg: add fulltest and shell entries with console=serial
- Update fulltest.c references: /bin/init.elf → /sbin/fulltest,
  /bin/echo.elf → /bin/echo, /bin/pie_test.elf → /bin/pie_test

Tests: 89/89 smoke (9s), cppcheck clean

6 weeks agofix: KVA_IOAPIC VA collision with BSS — move from 0xC0201000 to 0xC0401000
Tulio A M Mendes [Tue, 17 Feb 2026 00:45:26 +0000 (21:45 -0300)]
fix: KVA_IOAPIC VA collision with BSS — move from 0xC0201000 to 0xC0401000

Root cause: multiboot_copy (64KB static buffer) starts at VA 0xC0200FE0,
spanning pages 0xC0200000-0xC0210000. KVA_IOAPIC at 0xC0201000 mapped
IOAPIC MMIO over the BSS page containing the multiboot2 cmdline tag data.
After arch_platform_setup, reading bi->cmdline returned IOAPIC register
data (zeros) instead of the original cmdline string.

Symptom: [CMDLINE] "" regardless of GRUB menu entry selected.
Classic Heisenbug — adding a debug kprintf before IOAPIC init read the
correct data, masking the corruption.

Fix: move KVA_IOAPIC to 0xC0401000 (next to LAPIC at 0xC0400000),
well past _end at 0xC0265728. Updated VA map comment to reflect
current BSS extent (~0xC0266000).

6 weeks agofix: VirtualBox compatibility — UART detection, alarm timing, usermode segments,...
Tulio A M Mendes [Tue, 17 Feb 2026 00:12:25 +0000 (21:12 -0300)]
fix: VirtualBox compatibility — UART detection, alarm timing, usermode segments, user-mode #GP

4 fixes for VirtualBox compatibility + 1 cosmetic:

1. UART hardware detection (fixes boot freeze with serial disabled)
   - hal_uart_init() now probes the scratch register before configuring
   - All UART operations (putc, drain_rx, poll_rx, try_getc) guarded
     behind uart_present flag — prevents infinite loop on floating bus
   - console_init() auto-enables VGA when no UART detected so boot
     messages are visible
   - Added hal_uart_is_present() API + stubs for ARM/MIPS/RISC-V

2. alarm/SIGALRM test: replace 20M-iteration busy-loop with nanosleep
   polling (50ms × 40 = 2s max wait). Fast VirtualBox CPUs completed
   the busy-loop before the 1-second alarm fired.

3. x86_enter_usermode: load DS/ES/FS/GS=0x23 before iret to ring 3.
   Without this, iret nulls segment registers (kernel DPL=0 < new CPL=3
   per Intel SDM §6.12.1). On QEMU this was masked by early context
   switches that fixed DS via x86_enter_usermode_regs, but VirtualBox
   with Hyper-V acceleration may expose the race window.

4. User-mode exception handling: deliver SIGSEGV for any ring-3
   exception (#GP, #UD, etc.) instead of kernel panic. Previously only
   #PF (14) had this handling. A user-mode #GP now kills the process
   cleanly instead of halting the entire system.

5. LAPIC timer ticks printed in decimal instead of hex.

89/89 smoke tests pass, cppcheck clean.

6 weeks agotest: expand smoke suite to 89 tests + fix SMP orphan reparenting
Tulio A M Mendes [Mon, 16 Feb 2026 22:56:49 +0000 (19:56 -0300)]
test: expand smoke suite to 89 tests + fix SMP orphan reparenting

New userspace tests in init.c:
- E1: setuid/setgid/seteuid/setegid credential manipulation
- E2: fcntl F_GETFL/F_SETFL (O_NONBLOCK toggle)
- E3: fcntl F_GETFD/F_SETFD (FD_CLOEXEC)
- E4: sigsuspend (block SIGUSR1, self-signal, sigsuspend unblocks)
- E5: orphan reparenting (grandchild reparented to init after middle exits)
- Boot-time LZ4 Frame decompression pattern check

Kernel fix — SMP orphan reparenting:
- process_exit_notify() hardcoded parent_pid=1 for reparenting, but with
  SMP the AP idle processes consume PIDs 1-3 before the init userspace
  process is created (PID 4+).
- Added sched_set_init_pid() to register the actual init process PID.
- arch_platform.c calls sched_set_init_pid(current_process->pid) before
  entering userspace, so orphan reparenting targets the correct process.

89/89 smoke tests pass (9s), cppcheck clean.

6 weeks agofix: fork FD race condition + orphaned zombie memory leak
Tulio A M Mendes [Mon, 16 Feb 2026 22:32:09 +0000 (19:32 -0300)]
fix: fork FD race condition + orphaned zombie memory leak

Bug 1 — Fork FD race (HIGH severity):
  process_fork_create() enqueued the child to the runqueue under
  sched_lock, but syscall_fork_impl() copied file descriptors AFTER
  the function returned — with sched_lock released. On SMP, the child
  could be scheduled on another CPU and reach userspace before FDs
  were populated, seeing NULL file descriptors.

  Fix: move FD copying (with refcount bumps) into process_fork_create()
  itself, under sched_lock, before the child is enqueued. Added proper
  rollback of refcount bumps if kstack_alloc fails.

Bug 2 — Orphaned zombie leak (MEDIUM severity):
  When a process exited, its children were not reparented to PID 1
  (init). Zombie children of exited parents could never be reaped via
  waitpid, leaking process structs and kernel stacks forever.

  Fix: in process_exit_notify(), iterate the process list and reparent
  all children to PID 1. If any reparented child is already a zombie
  and init is blocked in waitpid(-1), wake init immediately.

Also verified (no bugs found):
- EOI handling correct (sent before handlers, spurious skips EOI)
- Lock ordering safe (all locks use irqsave, no cross-CPU ABBA)
- Heap has double-free and corruption detection
- User stack has guard pages

83/83 smoke tests pass, cppcheck clean.

6 weeks agofeat: LZ4 official Frame format for initrd compression/decompression
Tulio A M Mendes [Mon, 16 Feb 2026 22:24:18 +0000 (19:24 -0300)]
feat: LZ4 official Frame format for initrd compression/decompression

Replace custom 'LZ4B' block wrapper with the official LZ4 Frame format
(spec: https://github.com/lz4/lz4/blob/dev/doc/lz4_Frame_format.md).

Compressor (tools/mkinitrd.c):
- Write official frame: magic 0x184D2204, FLG/BD descriptor with
  content size and content checksum flags, xxHash-32 header checksum,
  data block, EndMark, xxHash-32 content checksum
- Fix block compressor MFLIMIT: last match must start >= 12 bytes
  before end of block (was 5, violating spec)

Decompressor (src/kernel/lz4.c):
- New lz4_decompress_frame() parses frame header, verifies header
  checksum via xxHash-32, decompresses blocks, verifies content checksum
- Existing lz4_decompress_block() unchanged (used internally)

Kernel initrd loader (src/drivers/initrd.c):
- Detect official LZ4 Frame magic (0x184D2204) first
- Keep legacy LZ4B detection as backward-compat fallback
- initrd_init() now takes size parameter for frame bounds checking

New files:
- include/xxhash32.h: standalone header-only xxHash-32 implementation

Cross-compatibility verified:
- 'lz4 -t initrd.img' validates our frame (official lz4 v1.9.4)
- 'lz4 -d initrd.img' decompresses correctly, tar lists all 12 files
- 83/83 smoke tests pass, cppcheck clean

6 weeks agofix: PMM total_memory overflow — MMAP reserved regions near 4GB inflated highest_addr
Tulio A M Mendes [Mon, 16 Feb 2026 22:08:36 +0000 (19:08 -0300)]
fix: PMM total_memory overflow — MMAP reserved regions near 4GB inflated highest_addr

Root cause: Multiboot2 MMAP includes a BIOS reserved region at
0xFFFC0000-0x100000000. The end address (0x100000000) overflows
uint32_t when stored in a uint64_t local variable, and (unsigned)
truncation yields 0 — hence '[PMM] total_memory bytes: 0x0'.

Fixes:
- Use uint32_t locals (32-bit x86 caps RAM at 512 MB anyway)
- Clamp MMAP end addresses to 0xFFFFFFFF before comparison
- Only track highest_avail from AVAILABLE regions, not reserved
- Use 'if' instead of 'else if' so both BASIC_MEMINFO and MMAP
  are processed in the same pass
- Print total_memory and freed_frames in decimal with MB suffix

Before: [PMM] total_memory bytes: 0x0
After:  [PMM] total_memory: 134086656 bytes (127 MB)

83/83 smoke tests pass, cppcheck clean.

6 weeks agofeat: SMP load balancing for fork/clone + IPI resched
Tulio A M Mendes [Mon, 16 Feb 2026 21:08:55 +0000 (18:08 -0300)]
feat: SMP load balancing for fork/clone + IPI resched

Enable load balancing in process_fork_create and process_clone_create:
both now dispatch to the least-loaded CPU via sched_pcpu_least_loaded().

All three process creation functions (create_kernel, fork, clone) now
send IPI_RESCHED to the target CPU after releasing sched_lock, waking
idle APs immediately when work is enqueued to their runqueue.

83/83 smoke tests pass in 9s, cppcheck clean.

6 weeks agofeat: SMP load balancing — per-CPU TSS, AP GDT reload, BSP-only timer work
Tulio A M Mendes [Mon, 16 Feb 2026 21:04:47 +0000 (18:04 -0300)]
feat: SMP load balancing — per-CPU TSS, AP GDT reload, BSP-only timer work

Three fixes enable kernel thread dispatch to any CPU:

1. Per-CPU TSS (gdt.c, gdt.h): Replace single TSS with tss_array[SMP_MAX_CPUS].
   Each AP gets its own TSS via tss_init_ap() so ring 3→0 transitions use
   the correct per-task kernel stack on any CPU.

2. AP GDT virtual base reload (smp.c): The AP trampoline loads the GDT with
   a physical base for real→protected mode. After paging is active, reload
   the GDTR with the virtual base and flush all segment registers. Without
   this, ring transitions on APs read GDT entries from the identity-mapped
   physical address, causing silent failures for user-mode processes.

3. BSP-only timer work (timer.c): Gate tick increment, vdso update,
   vga_flush, hal_uart_poll_rx, and process_wake_check to run only on
   CPU 0. APs only call schedule(). Prevents non-atomic tick races,
   concurrent VGA/UART access, and duplicate wake processing.

4. Per-CPU SYSENTER stacks (sysenter_init.c): Each AP gets its own
   SYSENTER ESP MSR pointing to a dedicated stack.

5. Load balancing (scheduler.c): process_create_kernel dispatches to
   the least-loaded CPU via sched_pcpu_least_loaded(). All CPUs update
   their own TSS ESP0 during context switch.

83/83 smoke tests pass, cppcheck clean.

6 weeks agofeat: IPI reschedule infrastructure (SMP Phase 4)
Tulio A M Mendes [Mon, 16 Feb 2026 19:14:42 +0000 (16:14 -0300)]
feat: IPI reschedule infrastructure (SMP Phase 4)

Add inter-processor interrupt (IPI) reschedule mechanism:

- IPI vector 0xFD (253) registered in IDT + ISR assembly stub
- isr_handler dispatches vector 253: sends LAPIC EOI then calls
  schedule() on the receiving CPU
- sched_ipi_resched() sends IPI to wake a remote idle CPU when
  work is enqueued to its runqueue (avoids waking self)
- sched_enqueue_ready() sends IPI after enqueuing to remote CPU
- sched_pcpu_inc_load() called when enqueuing new kernel threads

All processes still dispatched to CPU 0 — per-CPU TSS is needed
before user processes can run on APs.  The IPI + load tracking
infrastructure is ready for when per-CPU TSS is added.

83/83 smoke tests pass (8s), cppcheck clean.

6 weeks agofeat: AP scheduler entry (SMP Phase 3)
Tulio A M Mendes [Mon, 16 Feb 2026 19:00:38 +0000 (16:00 -0300)]
feat: AP scheduler entry (SMP Phase 3)

Enable scheduling on Application Processors:

- Load IDT on APs via idt_load_ap() — root cause of AP crashes was
  missing lidt, causing triple-fault when LAPIC timer fires
- Create per-CPU idle process for each AP in sched_ap_init()
- Start LAPIC timer on APs using BSP-calibrated ticks (no PIT
  recalibration needed — all CPUs share same bus clock)
- AP timer handler calls schedule() for local CPU runqueue
- BSP signals APs via ap_sched_go flag after timer_init completes
- Allocations in sched_ap_init done outside sched_lock to avoid
  ABBA deadlock with heap lock
- TSS updates restricted to CPU 0 (shared TSS, only BSP runs
  user processes)
- AP stack increased to 8KB to match kernel thread stack size

All processes still assigned to CPU 0 — Phase 4 will add load
balancing to distribute processes across CPUs.

83/83 smoke tests pass (8s), cppcheck clean.

6 weeks agorefactor: per-CPU runqueue data structure (SMP Phase 2)
Tulio A M Mendes [Mon, 16 Feb 2026 18:26:26 +0000 (15:26 -0300)]
refactor: per-CPU runqueue data structure (SMP Phase 2)

Replace global rq_active/rq_expired with per-CPU runqueue array:

- struct cpu_rq: active/expired runqueue pair + idle process per CPU
- pcpu_rq[SCHED_MAX_CPUS] array replaces global runqueue pointers
- All enqueue/dequeue operations now index by process cpu_id field
- schedule() uses percpu_cpu_index() to select local CPU's runqueue
- process_init() initializes all CPU runqueues, sets pcpu_rq[0].idle
- Added cpu_id field to struct process (set to 0 for now)
- rq_pick_next() takes cpu parameter, swaps per-CPU active/expired
- All wake paths (kill, signal, sleep wake, exit_notify) enqueue
  to the target process's assigned CPU runqueue

All processes still assigned to CPU 0 — Phase 3/4 will activate
AP scheduling and load balancing.

83/83 smoke tests pass (9s), cppcheck clean.

6 weeks agorefactor: per-CPU current_process via GS segment (SMP Phase 1)
Tulio A M Mendes [Mon, 16 Feb 2026 18:17:26 +0000 (15:17 -0300)]
refactor: per-CPU current_process via GS segment (SMP Phase 1)

Replace the global current_process variable with per-CPU access
through the GS-based percpu_data structure on x86:

- process.h: #define current_process percpu_current() on x86,
  keeps extern fallback for non-x86
- scheduler.c: write sites use percpu_set_current()
- interrupts.S: ISR entry now reloads percpu GS by reading LAPIC ID
  from MMIO (0xC0400020) and looking up the correct GS selector in
  _percpu_gs_lut[256] — solves the chicken-and-egg problem of
  needing GS to find the CPU but GS being clobbered by user TLS
- percpu.c: _percpu_gs_lut lookup table populated during percpu_init()
- hal_cpu_set_tls: no longer loads GS immediately (would clobber
  kernel percpu GS); user TLS GS is restored on ISR exit via pop

This is the foundation for running the scheduler on AP cores.

83/83 smoke tests pass (9s), cppcheck clean.

6 weeks agofeat: USTAR+LZ4 compressed initrd
Tulio A M Mendes [Mon, 16 Feb 2026 18:03:36 +0000 (15:03 -0300)]
feat: USTAR+LZ4 compressed initrd

Add LZ4 block compression to the initrd pipeline:

- src/kernel/lz4.c + include/lz4.h: standalone LZ4 block decompressor
  (~80 lines, no external dependencies)
- src/drivers/initrd.c: auto-detect LZ4B magic at boot, decompress
  into heap buffer, then parse the contained USTAR tar as before
- tools/mkinitrd.c: built-in LZ4 block compressor (greedy hash-table),
  builds tar in memory then wraps in LZ4B envelope
  (magic + orig_size + comp_size + compressed data)

Format: LZ4B header (12 bytes) + raw LZ4 block.  Falls back to
uncompressed tar if compression fails.

Results on current initrd (12 files including doom.elf):
  TAR: 562 KB -> LZ4B: 326 KB (58% ratio)

Backward compatible: kernel still accepts plain USTAR tar
(no LZ4B magic = parse directly).

83/83 smoke tests pass (10s), cppcheck clean.

6 weeks agofix: replace pmm_alloc_page_low with pmm_alloc_page — fix fork OOM
Tulio A M Mendes [Mon, 16 Feb 2026 17:47:10 +0000 (14:47 -0300)]
fix: replace pmm_alloc_page_low with pmm_alloc_page — fix fork OOM

The below-16MB page allocator (pmm_alloc_page_low) randomly sampled
pages and discarded any above 16MB.  With 100 zombie children holding
CoW address spaces, the low-memory pool exhausted and fork() returned
-ENOMEM, killing init before the SIGSEGV/waitpid-100/echo.elf tests.

On 32-bit PAE all physical addresses are below 4GB, so the 16MB
restriction is unnecessary for PDPTs, page directories, page tables,
and user frames.

Changes:
- vmm.c: replace all pmm_alloc_page_low() with pmm_alloc_page(),
  remove the dead pmm_alloc_page_low function
- usermode.c: replace pmm_alloc_page_low_16mb() with pmm_alloc_page(),
  remove the dead function
- init.c: make SIGSEGV test failure non-fatal (goto instead of
  sys_exit) so subsequent tests still run

83/83 smoke tests pass (10s), cppcheck clean.

6 weeks agofeat: PLT/GOT lazy binding — userspace resolver trampoline
Tulio A M Mendes [Mon, 16 Feb 2026 17:23:24 +0000 (14:23 -0300)]
feat: PLT/GOT lazy binding — userspace resolver trampoline

Kernel (elf.c):
- Skip R_386_JMP_SLOT relocations when PT_INTERP present (let ld.so resolve lazily)
- Load DT_NEEDED shared libraries at SHLIB_BASE (0x11000000)
- Support ET_EXEC and ET_DYN interpreters with correct base offset
- Fix AT_PHDR auxv computation for PIE binaries
- Store auxv in static buffer for execve to push in correct stack position
- Use pmm_alloc_page() instead of restrictive low-16MB allocator

Execve (syscall.c):
- Push auxv entries right after envp[] (Linux stack layout convention)
  so ld.so can find them by walking argc → argv[] → envp[] → auxv

ld.so (ldso.c):
- Complete rewrite for lazy PLT/GOT binding
- Parse auxv (AT_ENTRY, AT_PHDR, AT_PHNUM, AT_PHENT)
- Find PT_DYNAMIC, extract DT_PLTGOT/DT_JMPREL/DT_PLTRELSZ/DT_SYMTAB/DT_STRTAB
- Set GOT[1]=link_map, GOT[2]=_dl_runtime_resolve trampoline
- Implement _dl_runtime_resolve asm trampoline + dl_fixup C resolver
- Symbol lookup in shared library via DT_HASH at SHLIB_BASE
- Compiled as non-PIC ET_EXEC at INTERP_BASE (0x12000000)

VMM (vmm.c):
- Use pmm_alloc_page() for page table allocation (PAE PTs can be anywhere)

Test infrastructure:
- PIE test binary (pie_main.c) calls test_add() from libpietest.so via PLT
- Shared library (pie_func.c) provides test_add()
- Smoke test patterns for lazy PLT OK + PLT cached OK
- 80/83 smoke tests pass, cppcheck clean

6 weeks agofeat: EPOLLET edge-triggered epoll mode with smoke test (81/81)
Tulio A M Mendes [Mon, 16 Feb 2026 15:49:22 +0000 (12:49 -0300)]
feat: EPOLLET edge-triggered epoll mode with smoke test (81/81)

6 weeks agodocs: update all documentation for 80-test smoke suite + Rump Kernel status
Tulio A M Mendes [Mon, 16 Feb 2026 01:51:09 +0000 (22:51 -0300)]
docs: update all documentation for 80-test smoke suite + Rump Kernel status

- README.md: 44→80 smoke tests, add condition variables/TSC ns clock/IRQ chaining/
  FPU-SSE/Rump Kernel scaffold to features, update Status section, add src/rump/
  to directory structure
- POSIX_ROADMAP.md: 44→80 test count, update clock_gettime to TSC ns precision,
  update flock description, add Rump Kernel phases to Remaining Work
- TESTING_PLAN.md: 44→80 test count with expanded test categories
- SUPPLEMENTARY_ANALYSIS.md: add Rump Kernel to remaining enhancements,
  update test counts in conclusion

6 weeks agofeat: expand test battery from 44 to 80 smoke tests
Tulio A M Mendes [Mon, 16 Feb 2026 01:49:24 +0000 (22:49 -0300)]
feat: expand test battery from 44 to 80 smoke tests

New init.elf tests (D1-D15):
- nanosleep (50ms sleep + monotonic clock verification)
- CLOCK_REALTIME (nonzero epoch timestamp)
- /dev/urandom read
- /proc/cmdline read
- CoW fork (child write doesn't corrupt parent)
- readv/writev (scatter-gather I/O on pipe)
- fsync (write + sync on diskfs)
- truncate (path-based length reduction)
- getuid/getgid/geteuid/getegid (identity consistency)
- chmod (mode change on diskfs file)
- flock (LOCK_EX + LOCK_UN)
- times (process accounting)
- gettid (== getpid for main thread)
- posix_spawn (fork+exec echo.elf)
- clock_ns precision (TSC sub-10ms resolution)

Newly tracked existing tests (~15):
- pipe rw, ioctl tty, job control, poll /dev/null, pty,
  setsid/setpgid, sigaction SIGUSR1, sigreturn, tmpfs/mount,
  /dev/null, isatty, O_NONBLOCK, pipe2/dup3, chdir/getcwd,
  *at syscalls, rename/rmdir, getdents multi-fs, getppid,
  waitpid WNOHANG, SIGSEGV handler, waitpid 100 children,
  echo.elf execve

Smoke timeout raised from 90s to 120s.
80/80 smoke tests pass, cppcheck clean.

6 weeks agofeat: Rump Kernel hypercall scaffold + roadmap documentation
Tulio A M Mendes [Mon, 16 Feb 2026 01:17:43 +0000 (22:17 -0300)]
feat: Rump Kernel hypercall scaffold + roadmap documentation

- docs/RUMP_KERNEL_ROADMAP.md: full integration plan with 4 phases
  (filesystem → network → USB → audio), API mapping table,
  prerequisites checklist, and build integration notes
- src/rump/rumpuser_adros.c: Phase 1+3 hypercall scaffold implementing:
  rumpuser_init, rumpuser_malloc/free, rumpuser_putchar/dprintf,
  rumpuser_exit, rumpuser_getparam, rumpuser_getrandom,
  rumpuser_clock_gettime/sleep
- Phase 2 (threads/sync) and Phase 4 (file/block I/O) are TODO stubs

6 weeks agofeat: Rump Kernel prerequisites — condition variables, TSC nanosecond clock, IRQ...
Tulio A M Mendes [Mon, 16 Feb 2026 01:13:15 +0000 (22:13 -0300)]
feat: Rump Kernel prerequisites — condition variables, TSC nanosecond clock, IRQ chaining

Condition Variables (kcond_t):
- kcond_init/wait/signal/broadcast in sync.c
- kcond_wait atomically releases mutex, blocks, re-acquires on wakeup
- Supports timeout (ms) via PROCESS_SLEEPING + wake_at_tick
- Required by rumpuser for driver sleep/wake patterns

TSC-based Nanosecond Clock:
- TSC calibrated during LAPIC timer PIT measurement window
- clock_gettime_ns() returns nanoseconds since boot via rdtsc
- Falls back to tick-based 10ms granularity if TSC unavailable
- CLOCK_MONOTONIC syscall now uses nanosecond precision
- Linked against libgcc.a for 64-bit division on i386

Shared IRQ Handling (IRQ Chaining):
- Static pool of 32 irq_chain_node entries for shared vectors
- register_interrupt_handler auto-chains when vector already has handler
- unregister_interrupt_handler removes handler from chain
- isr_handler dispatches to all chained handlers for shared IRQs
- Transparent: single-handler fast path preserved (legacy slot)
- Required for PCI IRQ sharing and Rump Kernel driver integration

6 weeks agofeat: FPU/SSE context save/restore for correct floating-point across context switches
Tulio A M Mendes [Mon, 16 Feb 2026 00:45:17 +0000 (21:45 -0300)]
feat: FPU/SSE context save/restore for correct floating-point across context switches

- arch_fpu_init(): initialize x87 FPU (CR0.NE, clear EM/TS), enable OSFXSR if FXSR supported
- arch_fpu_save/restore: FXSAVE/FXRSTOR (or FSAVE/FRSTOR fallback) per process
- FPU state (512B) added to struct process, initialized for new processes
- fork/clone inherit parent FPU state; kernel threads get clean state
- schedule() saves prev FPU state before context_switch, restores next after
- Heap header padded 8->16 bytes for 16-byte aligned kmalloc (FXSAVE requirement)
- Added -mno-sse -mno-mmx to kernel ARCH_CFLAGS (prevent SSE in kernel code)
- Weak stubs in src/kernel/fpu.c for non-x86 architectures

6 weeks agofix: raise PROCESS_MAX_FILES from 16 to 64 for POSIX compliance
Tulio A M Mendes [Mon, 16 Feb 2026 00:33:23 +0000 (21:33 -0300)]
fix: raise PROCESS_MAX_FILES from 16 to 64 for POSIX compliance

7 weeks agodocs: update all documentation — 75 features, 44 smoke tests, epoll/inotify/aio/sendm...
Tulio A M Mendes [Sun, 15 Feb 2026 08:45:21 +0000 (05:45 -0300)]
docs: update all documentation — 75 features, 44 smoke tests, epoll/inotify/aio/sendmsg/recvmsg/pivot_root/VMM spinlock/spinlock debug

7 weeks agotest: expand smoke tests to 44 — add epoll, inotify, aio_* coverage
Tulio A M Mendes [Sun, 15 Feb 2026 08:39:03 +0000 (05:39 -0300)]
test: expand smoke tests to 44 — add epoll, inotify, aio_* coverage

7 weeks agofeat: aio_* — POSIX asynchronous I/O syscalls (aio_read/write/error/return/suspend)
Tulio A M Mendes [Sun, 15 Feb 2026 08:29:31 +0000 (05:29 -0300)]
feat: aio_* — POSIX asynchronous I/O syscalls (aio_read/write/error/return/suspend)

7 weeks agofeat: shared library lazy binding — functional ld.so with auxv parsing, PLT/GOT eager...
Tulio A M Mendes [Sun, 15 Feb 2026 08:25:12 +0000 (05:25 -0300)]
feat: shared library lazy binding — functional ld.so with auxv parsing, PLT/GOT eager relocation

7 weeks agofeat: pivot_root — syscall for swapping root filesystem
Tulio A M Mendes [Sun, 15 Feb 2026 08:18:03 +0000 (05:18 -0300)]
feat: pivot_root — syscall for swapping root filesystem

7 weeks agofeat: sendmsg/recvmsg — advanced socket I/O with scatter-gather iovec support
Tulio A M Mendes [Sun, 15 Feb 2026 08:14:34 +0000 (05:14 -0300)]
feat: sendmsg/recvmsg — advanced socket I/O with scatter-gather iovec support

7 weeks agofeat: inotify — inotify_init/add_watch/rm_watch syscalls for filesystem event monitoring
Tulio A M Mendes [Sun, 15 Feb 2026 08:05:16 +0000 (05:05 -0300)]
feat: inotify — inotify_init/add_watch/rm_watch syscalls for filesystem event monitoring

7 weeks agofeat: epoll — epoll_create/epoll_ctl/epoll_wait syscalls for scalable I/O event notif...
Tulio A M Mendes [Sun, 15 Feb 2026 07:56:15 +0000 (04:56 -0300)]
feat: epoll — epoll_create/epoll_ctl/epoll_wait syscalls for scalable I/O event notification

7 weeks agofeat: spinlock debug infrastructure — name, CPU ID tracking, deadlock detection
Tulio A M Mendes [Sun, 15 Feb 2026 07:48:49 +0000 (04:48 -0300)]
feat: spinlock debug infrastructure — name, CPU ID tracking, deadlock detection

7 weeks agofeat: vmm_find_free_area() — page-table-level scan for unmapped VA regions
Tulio A M Mendes [Sun, 15 Feb 2026 07:45:02 +0000 (04:45 -0300)]
feat: vmm_find_free_area() — page-table-level scan for unmapped VA regions

7 weeks agofeat: VMM spinlock for SMP-safe page table operations
Tulio A M Mendes [Sun, 15 Feb 2026 07:41:51 +0000 (04:41 -0300)]
feat: VMM spinlock for SMP-safe page table operations

7 weeks agodocs: audit update — all gaps resolved, score 93→98%, fix ld.so [~]→[x], tests 35→41
Tulio A M Mendes [Sun, 15 Feb 2026 07:27:56 +0000 (04:27 -0300)]
docs: audit update — all gaps resolved, score 93→98%, fix ld.so [~]→[x], tests 35→41

7 weeks agodocs: update README, BUILD_GUIDE, TESTING_PLAN for MIPS + expanded tests
Tulio A M Mendes [Sun, 15 Feb 2026 05:02:33 +0000 (02:02 -0300)]
docs: update README, BUILD_GUIDE, TESTING_PLAN for MIPS + expanded tests

- README.md: MIPS32 now boots on QEMU Malta, added run-mips instructions,
  updated test counts (41 smoke, 19 host), added src/arch/mips/ to directory
- BUILD_GUIDE.md: added section 6 (MIPS32 build & run), renumbered troubleshooting
- TESTING_PLAN.md: updated smoke test count to 41, added 6 new test descriptions,
  added qemu-system-mipsel to tools table, added make run-mips target

7 weeks agotest: expand smoke tests from 35 to 41 checks
Tulio A M Mendes [Sun, 15 Feb 2026 04:57:35 +0000 (01:57 -0300)]
test: expand smoke tests from 35 to 41 checks

New init.elf test cases:
- umask: set/get round-trip verification
- pipe capacity: F_GETPIPE_SZ / F_SETPIPE_SZ (fixed constant 1033)
- waitid: P_PID + WEXITED on forked child
- setitimer/getitimer: ITIMER_REAL set, query, cancel
- select regfile: select() on regular file returns immediately readable
- poll regfile: poll() on regular file returns POLLIN

Added syscall wrappers: sys_umask, sys_setitimer, sys_getitimer, sys_waitid
Added types: struct timeval, struct itimerval, P_ALL/P_PID/P_PGID/WEXITED

smoke_test.exp: 6 new patterns added (41 total)

Results: 41/41 smoke, 16/16 battery, 47/47 host tests pass

7 weeks agorefactor: move kernel_va_map.h to include/arch/x86/, clean virtio_blk.c port I/O
Tulio A M Mendes [Sun, 15 Feb 2026 04:38:44 +0000 (01:38 -0300)]
refactor: move kernel_va_map.h to include/arch/x86/, clean virtio_blk.c port I/O

- kernel_va_map.h: moved from include/ to include/arch/x86/ since it
  contains x86-specific VA layout (IOAPIC, LAPIC, ATA DMA, E1000)
- Updated all 8 include sites to use new path
- virtio_blk.c: removed duplicated port I/O inline asm, now uses
  io.h → arch/x86/io.h (outb/inb/outw/inw/outl/inl)
- Renamed outb_port/inb_port to standard outb/inb

Deep search results — agnostic areas verified clean:
- src/kernel/: no arch-specific code
- src/mm/: no arch-specific code
- src/drivers/: no arch-specific code (after virtio_blk fix)
- src/net/: no arch-specific code
- include/ (excl arch/): only dispatcher-pattern #includes remain
  (io.h, interrupts.h, arch_types.h, arch_syscall.h, spinlock.h)

7 weeks agofeat: MIPS32 bring-up + refactor spinlock.h arch separation
Tulio A M Mendes [Sun, 15 Feb 2026 04:33:01 +0000 (01:33 -0300)]
feat: MIPS32 bring-up + refactor spinlock.h arch separation

MIPS bring-up (T20):
- src/arch/mips/boot.S: BSS zeroing, di/ehb interrupt disable, .set mips32r2
- src/arch/mips/stubs.c: UART console, VGA no-ops, kernel subsystem stubs
- src/arch/mips/linker.ld: proper sections, BSS markers, discard MIPS ABI sections
- src/arch/mips/arch_early_setup.c: boot message for Malta
- src/hal/mips/uart.c: fix UART base 0xBFD003F8 → 0xB80003F8 (ISA I/O @ 0x18000000)
- src/hal/mips/usermode.c: fix type mismatch (const void*)
- Makefile: -mno-abicalls -fno-pic -G0 -march=mips32r2, run-mips target
- Boots on QEMU Malta with UART console output

spinlock.h refactor (T21):
- Extract arch-specific cpu_relax/irq_save/irq_restore into per-arch headers:
  include/arch/x86/spinlock.h, include/arch/arm/spinlock.h,
  include/arch/riscv/spinlock.h, include/arch/mips/spinlock.h
- spinlock.h now uses dispatcher pattern (#include arch/ARCH/spinlock.h)
- No inline asm remains in the agnostic header
- spinlock_t, spin_lock/unlock, TTAS logic remain agnostic

Verified: x86 35/35 smoke, 47/47 host tests, ARM64/RISC-V/MIPS boot on QEMU

7 weeks agodocs: update README, POSIX_ROADMAP, TESTING_PLAN, BUILD_GUIDE for all 66 features
Tulio A M Mendes [Sun, 15 Feb 2026 04:00:02 +0000 (01:00 -0300)]
docs: update README, POSIX_ROADMAP, TESTING_PLAN, BUILD_GUIDE for all 66 features

README.md:
- ARM64/RISC-V now listed as bootable on QEMU virt (not just build infra)
- Added SMAP, per-CPU runqueues, posix_spawn, interval timers, IPv6,
  DHCP, getaddrinfo, virtio-blk, dlopen/dlsym, sigqueue, waitid,
  POSIX mq_*/sem_*, pipe capacity fcntl, select/poll for files
- Running section now includes ARM64 and RISC-V commands
- Directory structure includes src/arch/arm/ and src/arch/riscv/
- Status updated to 66 total features, ~98% POSIX coverage

POSIX_ROADMAP.md:
- All 18 new features marked [x] in their respective tables
- Progress list extended to items 49-66
- Remaining Work section replaced: all gaps resolved, future
  enhancements listed (epoll, inotify, sendmsg/recvmsg, aio_*)

TESTING_PLAN.md:
- Added multi-arch build verification line
- Added qemu-system-aarch64 and qemu-system-riscv64 to tools table
- Added make run-arm / make run-riscv to Makefile targets

BUILD_GUIDE.md:
- Updated feature summary paragraph
- Fixed ld.so description (full relocation, not stub)
- ARM64 section: added make run-arm shortcut and expected output
- RISC-V section: fixed QEMU command (-bios none), added expected output
- Renumbered Common Troubleshooting to section 6

7 weeks agofeat: multi-arch ARM64/RISC-V bring-up with QEMU virt boot
Tulio A M Mendes [Sun, 15 Feb 2026 03:50:50 +0000 (00:50 -0300)]
feat: multi-arch ARM64/RISC-V bring-up with QEMU virt boot

ARM64 (AArch64):
- boot.S: EL2->EL1 transition, FP/SIMD enable (CPACR_EL1.FPEN),
  BSS zeroing, 16KB stack
- PL011 UART at 0x09000000 for serial console
- Linker script at 0x40000000 with proper section alignment
- Stubs for kernel subsystems not yet ported (PMM, VMM, scheduler,
  filesystem, syscalls, etc.)

RISC-V 64:
- boot.S: M-mode CSR init, BSS zeroing, 16KB stack
- NS16550 UART at 0x10000000 for serial console
- Linker script at 0x80000000 with proper section alignment
- Stubs matching ARM64 coverage

Build system:
- Makefile restructured: x86 gets full kernel/drivers/mm wildcards,
  ARM/RISC-V get minimal KERNEL_COMMON set (main, console, utils,
  cmdline, driver, cpu_features) + HAL + arch sources
- BOOT_OBJ now arch-specific (build/ARCH/arch/ARCH/boot.o)
- Added QEMU run targets: make run-arm, make run-riscv
- ARM64: -mno-outline-atomics to avoid libgcc atomic calls

Spinlock portability:
- Added AArch64 irq_save/irq_restore using DAIF register
- Simple volatile-flag spinlock for AArch64/RISC-V single-core
  bring-up (exclusive monitors need cacheable memory / MMU)

Key bug fix:
- AArch64 variadic functions (kprintf etc.) trap without FP/SIMD
  enabled — GCC saves q0-q7 in va_list register save area

Both architectures boot on QEMU virt and reach idle loop:
  make ARCH=arm && make run-arm
  make ARCH=riscv && make run-riscv

x86 unaffected: 35/35 smoke, 16/16 battery, cppcheck clean.

7 weeks agofeat: per-CPU scheduler runqueue infrastructure with load tracking
Tulio A M Mendes [Sun, 15 Feb 2026 02:47:29 +0000 (23:47 -0300)]
feat: per-CPU scheduler runqueue infrastructure with load tracking

- Add rq_load field to percpu_data struct (offset 20, struct stays 32 bytes)
- New sched_pcpu module: per-CPU load counters with atomic operations
  - sched_pcpu_init(): initialize for N CPUs after SMP enumeration
  - sched_pcpu_inc_load/dec_load(): lock-free load tracking
  - sched_pcpu_least_loaded(): find CPU with fewest ready processes
  - sched_pcpu_get_load(): query per-CPU load
- Integrate load tracking into scheduler enqueue/dequeue paths
- Wire up sched_pcpu_init() in arch_platform_setup after percpu_setup_gs
- All 35/35 smoke tests pass, 16/16 battery, cppcheck clean

7 weeks agofeat: dlopen/dlsym/dlclose syscalls for shared library loading
Tulio A M Mendes [Sun, 15 Feb 2026 01:58:23 +0000 (22:58 -0300)]
feat: dlopen/dlsym/dlclose syscalls for shared library loading

- SYSCALL_DLOPEN=109, SYSCALL_DLSYM=110, SYSCALL_DLCLOSE=111
- Loads ELF .so files into process address space at 0x30000000+
- Parses PT_DYNAMIC for SYMTAB/STRTAB/HASH to extract symbols
- Up to 8 concurrent libraries, 64 symbols each
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: IPv6 support via lwIP dual-stack
Tulio A M Mendes [Sun, 15 Feb 2026 01:47:41 +0000 (22:47 -0300)]
feat: IPv6 support via lwIP dual-stack

- Enabled LWIP_IPV6=1 with MLD, SLAAC autoconfig, ICMPv6, ND6
- Added all lwIP IPv6 source files (ethip6, icmp6, ip6, nd6, mld6, etc.)
- Fixed dual-stack IP4_ADDR usage in dns.c, net_ping.c, socket.c
  (use ip_2_ip4() + ip_addr_set_zero_ip4() for ip_addr_t)
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: full ld.so relocation processing in kernel ELF loader
Tulio A M Mendes [Sun, 15 Feb 2026 01:38:35 +0000 (22:38 -0300)]
feat: full ld.so relocation processing in kernel ELF loader

- Added elf32_process_relocations() to process PT_DYNAMIC segment
- Handles R_386_RELATIVE, R_386_GLOB_DAT, R_386_JMP_SLOT, R_386_32
- Called after segment loading for both main executable and interpreter
- Parses DT_REL, DT_RELSZ, DT_JMPREL, DT_PLTRELSZ, DT_SYMTAB
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: virtio-blk PCI legacy driver
Tulio A M Mendes [Sun, 15 Feb 2026 01:32:01 +0000 (22:32 -0300)]
feat: virtio-blk PCI legacy driver

- Detects virtio-blk device (vendor 0x1AF4, device 0x1001)
- Legacy PIO-based virtqueue with polling completion
- Read/write sector-at-a-time via 3-descriptor chain
- Registered as HAL_DRV_BLOCK priority 25
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofix: E1000 rx_thread scheduling — move sched_enqueue_ready outside sem lock
Tulio A M Mendes [Sun, 15 Feb 2026 01:22:20 +0000 (22:22 -0300)]
fix: E1000 rx_thread scheduling — move sched_enqueue_ready outside sem lock

- ksem_signal now calls sched_enqueue_ready after releasing the
  semaphore spinlock, avoiding lock-order issues when called from
  IRQ context (sched_enqueue_ready acquires sched_lock internally)
- Prevents potential deadlock: IRQ → ksem_signal → sched_lock
  while schedule() already holds sched_lock
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: DHCP client via lwIP (net_dhcp_start with 10s timeout)
Tulio A M Mendes [Sun, 15 Feb 2026 01:18:19 +0000 (22:18 -0300)]
feat: DHCP client via lwIP (net_dhcp_start with 10s timeout)

- Added dhcp.c and acd.c to lwIP build sources
- net_dhcp_start() starts DHCP on E1000 netif, waits up to 10s
- Falls back to static IP if DHCP times out
- LWIP_DHCP already enabled in lwipopts.h
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: getaddrinfo syscall with built-in hosts table + DNS fallback
Tulio A M Mendes [Sun, 15 Feb 2026 01:13:29 +0000 (22:13 -0300)]
feat: getaddrinfo syscall with built-in hosts table + DNS fallback

- SYSCALL_GETADDRINFO = 108
- Built-in localhost/localhost.localdomain -> 127.0.0.1
- Falls back to kernel dns_resolve() for other hostnames
- Returns IPv4 address in network byte order
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: POSIX named semaphores (sem_open, sem_close, sem_wait, sem_post, sem_unlink...
Tulio A M Mendes [Sun, 15 Feb 2026 01:09:55 +0000 (22:09 -0300)]
feat: POSIX named semaphores (sem_open, sem_close, sem_wait, sem_post, sem_unlink, sem_getvalue)

- 16 named semaphores with spinlock-protected value
- sem_wait spins with process_sleep(1) until value > 0
- SYSCALL_SEM_OPEN=102 through SYSCALL_SEM_GETVALUE=107
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: POSIX message queues (mq_open, mq_close, mq_send, mq_receive, mq_unlink)
Tulio A M Mendes [Sun, 15 Feb 2026 01:05:30 +0000 (22:05 -0300)]
feat: POSIX message queues (mq_open, mq_close, mq_send, mq_receive, mq_unlink)

- 8 named queues, 16 messages x 256 bytes each
- Spinlock-protected circular buffer per queue
- SYSCALL_MQ_OPEN=97, MQ_CLOSE=98, MQ_SEND=99, MQ_RECEIVE=100, MQ_UNLINK=101
- Added EMSGSIZE errno (90)
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: posix_spawn syscall (atomic fork+execve)
Tulio A M Mendes [Sun, 15 Feb 2026 00:58:29 +0000 (21:58 -0300)]
feat: posix_spawn syscall (atomic fork+execve)

- SYSCALL_POSIX_SPAWN = 96
- Combines fork + execve in one syscall call
- Returns 0 to parent, stores child PID via user pointer
- Child exits with 127 if execve fails
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: setitimer/getitimer syscalls (ITIMER_REAL, VIRTUAL, PROF)
Tulio A M Mendes [Sun, 15 Feb 2026 00:55:16 +0000 (21:55 -0300)]
feat: setitimer/getitimer syscalls (ITIMER_REAL, VIRTUAL, PROF)

- SYSCALL_SETITIMER = 92, SYSCALL_GETITIMER = 93
- ITIMER_REAL: uses alarm queue with repeating interval
- ITIMER_VIRTUAL: decrements on user-mode ticks, sends SIGVTALRM
- ITIMER_PROF: decrements on user+kernel ticks, sends SIGPROF
- Scheduler tick logic was already in place
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: sigqueue syscall (POSIX.1b signal with value)
Tulio A M Mendes [Sun, 15 Feb 2026 00:51:31 +0000 (21:51 -0300)]
feat: sigqueue syscall (POSIX.1b signal with value)

- SYSCALL_SIGQUEUE = 95, routes through process_kill
- si_value parameter acknowledged (bitmask pending model, not queued)
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: waitid syscall (P_ALL, P_PID) with siginfo_t output
Tulio A M Mendes [Sun, 15 Feb 2026 00:48:48 +0000 (21:48 -0300)]
feat: waitid syscall (P_ALL, P_PID) with siginfo_t output

- SYSCALL_WAITID = 94, wraps process_waitpid internally
- Fills minimal siginfo: si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid, si_status
- Supports P_ALL (idtype=0) and P_PID (idtype=1)
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: F_GETPIPE_SZ/F_SETPIPE_SZ pipe capacity control via fcntl
Tulio A M Mendes [Sun, 15 Feb 2026 00:45:30 +0000 (21:45 -0300)]
feat: F_GETPIPE_SZ/F_SETPIPE_SZ pipe capacity control via fcntl

- F_GETPIPE_SZ returns current pipe buffer capacity
- F_SETPIPE_SZ resizes pipe buffer (min 512, max 65536)
- Linearizes ring buffer data during resize
- Returns EBUSY if new size < current data count
- Added EBUSY errno (16)
- 35/35 smoke tests pass, cppcheck clean

7 weeks agofeat: enable SMAP (Supervisor Mode Access Prevention)
Tulio A M Mendes [Sun, 15 Feb 2026 00:41:33 +0000 (21:41 -0300)]
feat: enable SMAP (Supervisor Mode Access Prevention)

- STAC/CLAC bracket user memory accesses in copy_from_user/copy_to_user
- CR4.SMAP enabled when CPU supports it (CPUID leaf 7, EBX bit 20)
- g_smap_enabled runtime flag guards STAC/CLAC to avoid #UD on older CPUs
- Encoded as raw bytes (.byte 0x0F,0x01,0xCB/CA) for assembler compat
- 35/35 smoke tests pass, cppcheck clean

7 weeks agodocs: update README, POSIX_ROADMAP, TESTING_PLAN for 35-check smoke test battery
Tulio A M Mendes [Sun, 15 Feb 2026 00:09:35 +0000 (21:09 -0300)]
docs: update README, POSIX_ROADMAP, TESTING_PLAN for 35-check smoke test battery

- README: 35 QEMU smoke tests (was 20), 48 total features, test status
- POSIX_ROADMAP: init.elf test count updated to 35 checks
- TESTING_PLAN: smoke test count updated to 35

7 weeks agofeat: expand smoke test battery to 35 checks — add tests for brk, mmap, clock_gettime...
Tulio A M Mendes [Sun, 15 Feb 2026 00:08:02 +0000 (21:08 -0300)]
feat: expand smoke test battery to 35 checks — add tests for brk, mmap, clock_gettime, /dev/zero, /dev/random, procfs, pread/pwrite, ftruncate, symlink/readlink, access, sigprocmask/sigpending, alarm/SIGALRM, shmget/shmat/shmdt, O_APPEND, hard link

- Fix user-side struct termios to match kernel layout (was 4 bytes,
  kernel copies 27 bytes → stack corruption causing silent hang)
- Fix ICANON/ECHO values to match kernel defines (0x0002/0x0008)
- Fix sys_sigprocmask to pass mask by value (kernel ABI)
- Symlink test uses /tmp/ (tmpfs supports symlinks, diskfs does not)
- Hard link test is best-effort (diskfs link() may not work in all states)
- All 35/35 smoke tests pass in 11 seconds, cppcheck clean

7 weeks agofix: serial input blocking — timer-polled UART RX fallback
Tulio A M Mendes [Sat, 14 Feb 2026 22:27:14 +0000 (19:27 -0300)]
fix: serial input blocking — timer-polled UART RX fallback

Root cause: IOAPIC edge-triggered delivery for COM1 IRQ 4 never
fires in QEMU i440FX. The UART IRQ line state during the PIC→IOAPIC
transition is undefined — if the line is already HIGH when the
IOAPIC starts watching, no rising edge is ever detected, permanently
blocking serial input.

Attempted fixes that did NOT work:
- hal_uart_drain_rx() after IOAPIC routing (drain FIFO + IIR + MSR)
- FIFO trigger level 14→1 byte (eliminate character timeout dependency)
- IER disable→drain→re-enable sequencing around IOAPIC route

Fix: poll UART RX in the timer tick handler (100Hz). hal_uart_poll_rx()
checks LSR bit 0 and dispatches pending characters through the existing
rx_callback chain (tty_input_char). This gives ≤10ms latency for serial
input — imperceptible for interactive use.

The IRQ-driven path (uart_irq_handler at vector 36) remains active as
a fast path for platforms where IOAPIC edge detection works correctly.

Also adds tests/test_serial_input.exp: automated expect-based test that
boots /bin/sh with console=serial and verifies typed commands execute.

Tests: 20/20 smoke (8s), 16/16 battery, serial input PASS, cppcheck clean

7 weeks agofix: ISR GS clobber, serial IRQ stuck, ring3 page fault
Tulio A M Mendes [Sat, 14 Feb 2026 21:07:29 +0000 (18:07 -0300)]
fix: ISR GS clobber, serial IRQ stuck, ring3 page fault

1. **ISR GS clobber (III) — FIXED**
   - interrupts.S: save/restore GS separately instead of overwriting
     with 0x10. DS/ES/FS still set to kernel data, but GS now
     preserves the per-CPU selector across interrupt entry/exit.
   - struct registers: new 'gs' field at offset 0.
   - ARCH_REGS_SIZE: 64 → 68.
   - x86_enter_usermode_regs: updated all hardcoded register offsets
     (+4 for the new GS field).

2. **Serial keyboard blocking (II) — FIXED**
   - Root cause: hal_uart_init() runs early (under PIC), enabling
     UART RX interrupts. Later, IOAPIC routes IRQ 4 as edge-triggered.
     If any character arrived between PIC-era init and IOAPIC setup,
     the UART IRQ line stays asserted — the IOAPIC never sees a
     rising edge, permanently blocking all future serial input.
   - Fix: hal_uart_drain_rx() clears pending UART FIFO + IIR + MSR
     immediately after ioapic_route_irq(4, ...) to de-assert the
     IRQ line and allow future edges.

3. **Ring3 page fault at 0xae1000 (V) — FIXED**
   - The ring3 code emitter wrote to code_phys as a virtual address,
     relying on an identity mapping that doesn't exist for all
     physical addresses. Now uses P2V (phys + 0xC0000000) to access
     physical pages via the kernel's higher-half mapping.

Tests: 20/20 smoke (8s), 16/16 battery, cppcheck clean

7 weeks agofix: ring3 private address space + VTIME timer frequency regression
Tulio A M Mendes [Sat, 14 Feb 2026 20:14:44 +0000 (17:14 -0300)]
fix: ring3 private address space + VTIME timer frequency regression

1. **ring3 test: create private address space**
   - Previously, x86_usermode_test_start() mapped user pages at
     0x00400000 and 0x00800000 directly into kernel_as (shared by
     all kernel threads). These pages were never cleaned up on exit.
   - Now creates a private AS via vmm_as_create_kernel_clone(),
     switches to it, then maps user pages there. On process exit,
     vmm_as_destroy() properly frees the pages.
   - Eliminates kernel_as contamination that could interfere with
     other processes (init.elf, /bin/sh).

2. **TTY VTIME: fix hardcoded 50Hz tick rate**
   - tty_read_kbuf() calculated non-canonical VTIME timeout as
     vtime*5 (hardcoded for 50Hz). At 100Hz this gave half the
     intended timeout, causing premature read returns.
   - Now uses vtime*(TIMER_HZ/10) which is correct at any tick rate.

Tests: 20/20 smoke (8s), 16/16 battery, cppcheck clean

7 weeks agorefactor: proper time-slice scheduler + fix arch contamination + mask PIT
Tulio A M Mendes [Sat, 14 Feb 2026 08:24:36 +0000 (05:24 -0300)]
refactor: proper time-slice scheduler + fix arch contamination + mask PIT

Three fixes for the 100Hz timer upgrade:

1. **Arch contamination removed from drivers/timer.c**
   - Moved BSP-only guard (lapic_get_id check) from generic
     src/drivers/timer.c into src/hal/x86/timer.c where it belongs
   - drivers/timer.c now has zero #ifdef or arch-specific includes

2. **Proper time-slice scheduling replaces tick%2 hack**
   - Added time_slice field to struct process (SCHED_TIME_SLICE=2)
   - schedule() skips preemption while time_slice > 0, decrementing
     each tick. Voluntary yields (sleep/waitpid/sem) bypass the
     check entirely — only timer-driven preemption is rate-limited
   - Effective preemption rate: TIMER_HZ/SCHED_TIME_SLICE = 50Hz
   - Sleep/wake resolution remains at full 100Hz via process_wake_check

3. **PIT IRQ 0 masked when LAPIC timer is active**
   - ioapic_mask_irq(0) called before lapic_timer_start()
   - Eliminates ~18 extra ticks/sec from PIT double-ticking BSP
   - Tick counter now advances at exactly 100Hz, fixing ~18% timing
     error in all sleep/timing calculations

Tests: 20/20 smoke (8s), 16/16 battery, cppcheck clean

7 weeks agofeat: increase timer frequency to 100Hz (like Linux)
Tulio A M Mendes [Sat, 14 Feb 2026 07:58:29 +0000 (04:58 -0300)]
feat: increase timer frequency to 100Hz (like Linux)

- Central TIMER_HZ=100 and TIMER_MS_PER_TICK=10 constants in timer.h
- Replace all hardcoded 50Hz / 20ms-per-tick assumptions:
  syscall.c (nanosleep, clock_gettime), procfs.c (/proc/uptime),
  net_ping.c (sleep/time calculations), sys_arch.c (sys_now),
  sync.c (ksem_wait_timeout ms-to-ticks conversion)
- Use timer_init(TIMER_HZ) instead of hardcoded 50
- BSP-only timer tick via lapic_get_id() — APs return early to
  eliminate sched_lock/vga_lock contention at higher tick rates
- vga_flush() skips spinlock + cursor update when nothing dirty
- Preempt every 2nd tick (effective 50Hz preemption) to avoid
  excessive CR3 reloads in emulated environments (QEMU TLB flush
  overhead). Sleep/timing resolution remains at full 100Hz via
  process_wake_check on every tick.

Tests: 20/20 smoke (11s), 16/16 battery, cppcheck clean

7 weeks agofix: restore immediate VGA flush in vga_write_buf to fix ring3 display hang
Tulio A M Mendes [Sat, 14 Feb 2026 06:54:50 +0000 (03:54 -0300)]
fix: restore immediate VGA flush in vga_write_buf to fix ring3 display hang

The deferred-only VGA flush (timer tick at 50Hz) caused VGA output
to stop updating when the ring3 test was active. Restoring the
immediate flush after each write batch fixes the issue.

The shadow buffer still provides the key performance wins:
- Scrolling in RAM (memmove on shadow, not MMIO)
- Single cursor update per write batch (not per character)
- Dirty-region tracking (only modified cells flushed)

Tests: 20/20 smoke (11s), 16/16 battery, cppcheck clean.

7 weeks agoperf: VGA shadow buffer + batched TTY output — eliminates MMIO bottleneck
Tulio A M Mendes [Sat, 14 Feb 2026 05:56:56 +0000 (02:56 -0300)]
perf: VGA shadow buffer + batched TTY output — eliminates MMIO bottleneck

VGA console was extremely slow in QEMU because every character caused:
- 4 outb I/O port writes for cursor update
- Direct writes to VGA MMIO (0xB8000) which QEMU traps per-access
- Full-screen memmove on MMIO for each scroll

Three-layer optimization:

1. Shadow buffer: all VGA writes target a RAM shadow[] array. Only
   dirty cells are flushed to VGA MMIO. Scrolling uses RAM-speed
   memmove instead of MMIO memmove.

2. Batched TTY output: tty_write_kbuf/tty_write now OPOST-expand
   into a local buffer and call console_write_buf() once per chunk
   instead of console_put_char() per character. VGA cursor is
   updated once per batch, not per character.

3. Deferred flush: vga_write_buf() (bulk TTY path) does NOT flush
   to VGA MMIO at all. Screen is refreshed at 50Hz via vga_flush()
   called from the timer tick. Single-char paths (echo, kprintf)
   still flush immediately for responsiveness.

Result: 20/20 smoke tests in 8s WITHOUT console=serial (was timing
out at 90s before). The console=serial workaround is no longer
needed.

Files changed:
- src/drivers/vga_console.c: shadow buffer, dirty tracking, flush
- src/drivers/timer.c: periodic vga_flush() on tick
- src/kernel/tty.c: tty_opost_expand + console_write_buf batching
- src/kernel/console.c: new console_write_buf()
- include/vga_console.h: vga_write_buf, vga_flush declarations
- include/console.h: console_write_buf declaration
- iso/boot/grub/grub.cfg: removed console=serial workaround

7 weeks agofix: cmdline parsing, framebuffer fallback, UART serial input for TTY
Tulio A M Mendes [Sat, 14 Feb 2026 05:24:24 +0000 (02:24 -0300)]
fix: cmdline parsing, framebuffer fallback, UART serial input for TTY

1. cmdline: use separate tok_copy buffer for tokenization so token
   pointers are properly null-terminated; raw_copy stays pristine
   for /proc/cmdline.

2. framebuffer: remove Multiboot2 framebuffer request tag from boot.S
   so GRUB keeps EGA text mode (no pixel drawing routines yet).

3. serial input: enable UART RX interrupt (IER bit 0), route IRQ 4
   (COM1) via IOAPIC to IDT vector 36, wire hal_uart_set_rx_callback
   to tty_input_char in tty_init(). /bin/sh now accepts serial input.

4. grub.cfg: add shell entry (init=/bin/sh), keep ring3 test with
   console=serial for smoke test performance.

Tests: 20/20 smoke, cppcheck clean.

7 weeks agofix: cmdline parser, VBE framebuffer, VA collision, ring3 test, code audit
Tulio A M Mendes [Sat, 14 Feb 2026 04:17:00 +0000 (01:17 -0300)]
fix: cmdline parser, VBE framebuffer, VA collision, ring3 test, code audit

- fix(cmdline): don't skip token 0 when GRUB2+Multiboot2 omits kernel path
  GRUB2 may pass only arguments (e.g. 'ring3') without the kernel path.
  The parser now only skips token 0 if it starts with '/'.

- feat(vbe): add Multiboot2 framebuffer request tag to boot.S
  Requests 1024x768x32 linear framebuffer from GRUB (optional flag=1).
  Add fb_type field to boot_info for detecting framebuffer vs text mode.
  VGA text console conditionally disabled when linear framebuffer active.

- fix(va): hal_mm_map_physical_range used 0xE0000000 (KVA_FRAMEBUFFER)
  This caused the initrd mapping to be destroyed when VBE mapped the
  framebuffer at the same VA. Moved to KVA_PHYS_MAP at 0xDC000000.

- fix(ring3): run ring3 test in own kernel thread instead of PID 0
  x86_usermode_test_start() enters ring3 via iret and never returns.
  Previously hidden because ring3 flag was never recognized (cmdline bug).

- feat(console): wire console= cmdline parameter to console subsystem
  Supports console=serial, console=vga, console=ttyS0, console=tty0.

- refactor: use KVA_FRAMEBUFFER from kernel_va_map.h in vbe.c
- cleanup: replace inline extern rtc_unix_timestamp with #include rtc.h
- fix(multiboot2): remove break after MODULE tag to scan ALL tags

Build: clean. cppcheck: clean. Tests: 20/20 smoke, 47/47 host unit.

7 weeks agorefactor: remove namespace callbacks from struct file_operations
Tulio A M Mendes [Sat, 14 Feb 2026 03:24:52 +0000 (00:24 -0300)]
refactor: remove namespace callbacks from struct file_operations

Complete the file_operations / inode_operations separation:

- fs.h: struct file_operations now contains only per-fd I/O ops:
  read, write, open, close, ioctl, mmap, poll
- fs.h: struct inode_operations exclusively owns namespace/metadata:
  lookup, readdir, create, mkdir, unlink, rmdir, rename, truncate, link
- Migrated ext2 and initrd (missed in previous commits)
- Removed all f_ops fallback paths in fs.c, syscall.c, overlayfs.c,
  kconsole.c — everything now uses i_ops for namespace operations
- Clean separation: file_operations = fd I/O, inode_operations = namespace

All FSes migrated: diskfs, devfs, procfs, tmpfs, overlayfs, persistfs,
pty, fat, ext2, initrd.

cppcheck clean, 20/20 smoke tests pass.

7 weeks agorefactor: migrate pty and fat to inode_operations
Tulio A M Mendes [Sat, 14 Feb 2026 02:08:36 +0000 (23:08 -0300)]
refactor: migrate pty and fat to inode_operations

- pty: pty_pts_dir_iops with lookup/readdir; pty_pts_dir_fops now empty
- fat: fat_dir_iops with lookup/readdir/create/mkdir/unlink/rmdir/rename;
  fat_file_iops with truncate; fat_dir_fops and fat_file_fops keep only
  close and read/write/close respectively
- ext2 has no VFS integration yet, no migration needed

All node creation sites wire both f_ops and i_ops.

20/20 smoke tests pass.

7 weeks agorefactor: migrate devfs, procfs, tmpfs, overlayfs, persistfs to inode_operations
Tulio A M Mendes [Sat, 14 Feb 2026 01:57:20 +0000 (22:57 -0300)]
refactor: migrate devfs, procfs, tmpfs, overlayfs, persistfs to inode_operations

- devfs: devfs_dir_iops with lookup/readdir; devfs_dir_ops now empty
- procfs: procfs_root_iops, procfs_self_iops, procfs_pid_dir_iops
  with lookup/readdir; corresponding fops now empty
- tmpfs: tmpfs_dir_iops with lookup/readdir; tmpfs_dir_ops now empty;
  all dir creation sites (tmpfs_child_ensure_dir, tmpfs_create_root)
  wire i_ops
- overlayfs: overlay_dir_iops with lookup/readdir; finddir_impl and
  readdir_impl updated to check i_ops->lookup/readdir on child layers
  before falling back to f_ops (needed since child FSes now use i_ops)
- persistfs: persistfs_root_iops with lookup

All file-type nodes (read/write/poll/ioctl) remain in f_ops only —
correct separation of concerns.

20/20 smoke tests pass.

7 weeks agorefactor: migrate diskfs to inode_operations
Tulio A M Mendes [Sat, 14 Feb 2026 01:33:58 +0000 (22:33 -0300)]
refactor: migrate diskfs to inode_operations

- diskfs_dir_iops: lookup, readdir, create, mkdir, unlink, rmdir,
  rename, link (moved from diskfs_dir_fops)
- diskfs_file_iops: truncate (moved from diskfs_file_fops)
- diskfs_dir_fops: only close remains
- diskfs_file_fops: only read, write, close remain
- All node creation sites wire both f_ops and i_ops

20/20 smoke tests pass.

7 weeks agorefactor: add struct inode_operations + VFS dispatch with fallback
Tulio A M Mendes [Sat, 14 Feb 2026 01:19:42 +0000 (22:19 -0300)]
refactor: add struct inode_operations + VFS dispatch with fallback

Infrastructure for separating file_operations (per-fd I/O) from
inode_operations (namespace/metadata):

- fs.h: added struct inode_operations with lookup, readdir, create,
  mkdir, unlink, rmdir, rename, truncate, link callbacks
- fs.h: added i_ops pointer to fs_node_t alongside existing f_ops
- fs.c: VFS dispatch checks i_ops first, falls back to f_ops for
  all namespace operations (lookup, create, mkdir, unlink, rmdir,
  rename, truncate, link)
- syscall.c: getdents dispatch checks i_ops->readdir first

This is backward-compatible: all existing filesystems continue to
work through the f_ops fallback path. Each FS will be migrated
individually in subsequent commits.

20/20 smoke tests pass.

7 weeks agofeat: fcntl record locking (F_GETLK/F_SETLK/F_SETLKW) + F_DUPFD_CLOEXEC
Tulio A M Mendes [Sat, 14 Feb 2026 00:23:03 +0000 (21:23 -0300)]
feat: fcntl record locking (F_GETLK/F_SETLK/F_SETLKW) + F_DUPFD_CLOEXEC

POSIX byte-range advisory record locking via fcntl():

- syscall.c: rlock_table (64 entries) with spinlock-protected byte-range
  lock management supporting F_RDLCK (shared), F_WRLCK (exclusive), F_UNLCK
- rlock_conflicts(): detects overlapping conflicting locks from other pids
- rlock_setlk(): acquires/releases byte-range locks with optional blocking
- rlock_release_pid(): releases all record locks on process exit
- F_GETLK: returns conflicting lock info or F_UNLCK if no conflict
- F_SETLK: non-blocking lock acquisition (returns EAGAIN on conflict)
- F_SETLKW: blocking lock acquisition (sleeps until lock available)
- F_DUPFD_CLOEXEC: dup fd with close-on-exec flag set

Userland:
- fcntl.h: expanded with F_GETLK/F_SETLK/F_SETLKW, F_DUPFD_CLOEXEC,
  FD_CLOEXEC, O_CLOEXEC, F_RDLCK/F_WRLCK/F_UNLCK, struct flock

This completes the file locking infrastructure needed by SQLite and
POSIX-compliant applications.

20/20 smoke tests pass.

7 weeks agofeat: real advisory file locking (flock) replacing no-op stub
Tulio A M Mendes [Sat, 14 Feb 2026 00:02:38 +0000 (21:02 -0300)]
feat: real advisory file locking (flock) replacing no-op stub

- syscall.c: implemented flock_table (64 entries) with spinlock-protected
  advisory locking supporting LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB
- flock_do(): handles shared/exclusive acquisition with conflict detection,
  upgrade/downgrade of existing locks, blocking retry via process_sleep
- flock_release_pid(): releases all locks for a process on exit
- SYSCALL_EXIT: calls flock_release_pid before closing files
- errno.h: added ENOLCK=37, EWOULDBLOCK=EAGAIN (kernel + userland)
- sys/file.h: new userland header with LOCK_SH/EX/NB/UN defines

Previously flock() was a silent no-op returning 0. Now it provides
real advisory locking semantics needed by SQLite and daemons.

20/20 smoke tests pass.

7 weeks agofeat: socket poll support — wire ksocket_poll into sock_fops
Tulio A M Mendes [Fri, 13 Feb 2026 23:52:38 +0000 (20:52 -0300)]
feat: socket poll support — wire ksocket_poll into sock_fops

poll()/select() now works correctly on socket file descriptors.

- socket.c: added ksocket_poll() that checks socket readiness based
  on state (CONNECTED/LISTENING/PEER_CLOSED), rx_count, aq_count,
  and error flag; returns VFS_POLL_IN/OUT/ERR/HUP as appropriate
- socket.h: declared ksocket_poll()
- syscall.c: added sock_node_poll() wrapper and wired .poll into
  sock_fops — sockets now participate in the generic f_ops->poll
  dispatch path in poll_wait_kfds

Previously socket fds in poll/select silently reported ready via
the fallback path. Now they report actual readiness.

20/20 smoke tests pass.

7 weeks agofeat: setitimer/getitimer syscalls with repeating interval timer support
Tulio A M Mendes [Fri, 13 Feb 2026 22:45:48 +0000 (19:45 -0300)]
feat: setitimer/getitimer syscalls with repeating interval timer support

Phase D2+D3 complete — implements POSIX interval timers.

Kernel changes:
- process.h: added alarm_interval (repeat ticks for ITIMER_REAL),
  itimer_virt_value/interval, itimer_prof_value/interval fields
- scheduler.c process_wake_check: alarm queue now auto-requeues
  repeating timers (alarm_interval != 0) on expiry; added per-tick
  ITIMER_VIRTUAL (user mode) and ITIMER_PROF (user+kernel) accounting
  that delivers SIGVTALRM/SIGPROF respectively
- syscall.c: implemented SYSCALL_SETITIMER (92) and SYSCALL_GETITIMER (93)
  supporting ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF; alarm() updated
  to clear alarm_interval (one-shot only) and use TICKS_PER_SEC constant
- syscall.h: added SYSCALL_SETITIMER=92, SYSCALL_GETITIMER=93

Userland:
- signal.h: added SIGVTALRM=26, SIGPROF=27
- sys/time.h: new header with struct timeval, struct itimerval,
  ITIMER_REAL/VIRTUAL/PROF defines, setitimer/getitimer prototypes
- syscall.h: added SYS_SETITIMER=92, SYS_GETITIMER=93
- unistd.c: added setitimer() and getitimer() wrappers

Timer resolution: 20ms (50Hz tick). Conversions use
USEC_PER_TICK=20000 for timeval<->ticks.

20/20 smoke tests pass.

7 weeks agorefactor: replace O(N) alarm scan with O(1) sorted alarm queue
Tulio A M Mendes [Fri, 13 Feb 2026 22:34:04 +0000 (19:34 -0300)]
refactor: replace O(N) alarm scan with O(1) sorted alarm queue

Phase D1 complete — alarm delivery now uses a sorted doubly-linked
queue identical in design to the sleep queue.

- process.h: added alarm_next, alarm_prev, in_alarm_queue fields
- scheduler.c: added alarm_queue_insert/alarm_queue_remove helpers,
  alarm_head pointer, and public process_alarm_set() API
- process_wake_check: replaced O(N) scan of all processes with O(1)
  pop from sorted alarm queue head
- syscall.c: alarm() syscall now routes through process_alarm_set()
  which atomically manages the queue under sched_lock
- Alarm queue cleanup on process exit (process_exit_notify) and
  signal kill (SIG_KILL path)

20/20 smoke tests pass.

7 weeks agorefactor: remove legacy per-node function pointers from fs_node_t
Tulio A M Mendes [Fri, 13 Feb 2026 22:27:06 +0000 (19:27 -0300)]
refactor: remove legacy per-node function pointers from fs_node_t

Phase B3b complete — all VFS dispatch now goes exclusively through
the f_ops pointer. Changes:

- fs.h: removed 16 legacy function pointer fields from fs_node_t
  (read, write, open, close, finddir, readdir, ioctl, mmap, poll,
  create, mkdir, unlink, rmdir, rename, truncate, link)
  fs_node_t shrinks by 64 bytes (16 pointers × 4 bytes)

- fs.c: removed all legacy fallback paths from VFS wrappers
- syscall.c: removed legacy fallbacks for poll, readdir, ioctl,
  mmap, truncate, finddir, and read/write capability checks
- overlayfs.c: removed legacy fallbacks in finddir/readdir dispatch
- kconsole.c: switched to f_ops-based readdir dispatch

- Removed all dual-assignment lines from:
  ext2.c, fat.c, diskfs.c, tmpfs.c, devfs.c, overlayfs.c,
  procfs.c, persistfs.c, pty.c, tty.c, keyboard.c, vbe.c,
  initrd.c, syscall.c (pipe + socket nodes)

- Removed ext2_set_dir_ops, fat_set_dir_ops, diskfs_set_dir_ops
  helper functions (no longer needed)

20/20 smoke tests pass.

7 weeks agorefactor: migrate procfs, persistfs, pty to f_ops (dual-assignment)
Tulio A M Mendes [Fri, 13 Feb 2026 21:59:28 +0000 (18:59 -0300)]
refactor: migrate procfs, persistfs, pty to f_ops (dual-assignment)

- procfs: 9 static file_operations tables for root, self, self/status,
  uptime, meminfo, cmdline, pid dirs, pid/status, pid/maps
- persistfs: 2 tables for root dir and counter file
- pty: 4 tables for master, slave, ptmx, pts directory

All fs_node_t nodes in the codebase now have f_ops assigned.
Legacy per-node function pointers retained for backward compat.

20/20 smoke tests pass.