Tulio A M Mendes [Fri, 13 Feb 2026 04:07:06 +0000 (01:07 -0300)]
refactor: replace doubly-linked-list heap with buddy allocator
Power-of-2 block sizes from 2^5 (32B) to 2^23 (8MB) with O(log N)
alloc/free and automatic buddy coalescing on free.
Design:
- block_hdr_t (8B) at the start of every block: magic, order, is_free
- Free blocks embed circular doubly-linked list pointers in their
data area (free_node_t) for O(1) insert/remove per order
- 19 free lists (one per order, sentinel-based)
- Buddy merge: XOR offset to find buddy, check magic + is_free + order
- Spinlock-protected for SMP safety
Allocation:
- size_to_order: find smallest 2^k >= size + 8 (header)
- Search free lists from requested order upward
- Split larger blocks down, placing upper buddies on their free lists
Deallocation:
- Verify magic and double-free
- Iteratively merge with buddy while buddy is free at same order
- Insert merged block into correct free list
Trade-offs vs previous doubly-linked-list allocator:
+ O(log N) worst case vs O(N) first-fit scan
+ No external fragmentation (buddy coalescing)
+ Deterministic allocation time
- Internal fragmentation from power-of-2 rounding (~50% worst case)
- Fixed 8MB heap (was 10MB growable to 64MB)
Updated smoke test expectation for new init message.
19/19 smoke tests pass, cppcheck clean.
Network init (e1000_netif.c):
- tcpip_init(callback, NULL) with volatile flag polling for sync
- netif input changed from ethernet_input to tcpip_input
- net_poll no longer calls sys_check_timeouts (handled by tcpip_thread)
Kernel stack enlargement (scheduler.c):
- Increased from 4KB (1 page) to 8KB (2 pages) per thread
- Required for deeper call chains in lwIP threaded mode
- Updated kstack_alloc, kstack_free, and all stack+offset references
LAPIC VA relocation (lapic.c):
- Moved from 0xC0200000 to 0xC0400000 to avoid collision with
enlarged kernel BSS (~764KB with NO_SYS=0 memp pools)
lwIP third-party patch (patches/lwip-tcpip-volatile.patch):
- tcpip_init_done and tcpip_init_done_arg marked volatile in tcpip.c
- Fixes cross-thread visibility: compiler was caching NULL from BSS
init, preventing tcpip_thread from seeing the callback set by
tcpip_init in the init thread
Tulio A M Mendes [Fri, 13 Feb 2026 01:09:53 +0000 (22:09 -0300)]
refactor: abstract x86 register accesses in syscall dispatcher via sc_* macros
- include/arch/x86/arch_syscall.h: define sc_num/sc_arg0..4/sc_ret/
sc_ip/sc_usp macros mapping to x86 INT 0x80 ABI registers
(eax/ebx/ecx/edx/esi/edi/eip/useresp)
- include/arch_syscall.h: generic dispatch header with non-x86 stubs
- src/kernel/syscall.c: replace all ~200 direct regs->eax/ebx/ecx/
edx/esi/edi/eip/useresp accesses with arch-agnostic sc_* macros
across syscall_handler, posix_ext_syscall_dispatch, and
socket_syscall_dispatch
syscall.c now contains zero x86-specific register names. To port to
ARM, only arch/arm/arch_syscall.h needs to map sc_* to ARM registers
(r7/r0-r4/pc/sp).
Tulio A M Mendes [Fri, 13 Feb 2026 00:43:13 +0000 (21:43 -0300)]
refactor: remove /disk/ VFS bypass from syscall.c — route through VFS mount + callbacks
- fs.h: add create/mkdir/unlink/rmdir/rename/truncate callbacks to fs_node_t
- fs.h: add vfs_lookup_parent, vfs_create, vfs_mkdir, vfs_unlink, vfs_rmdir,
vfs_rename, vfs_truncate prototypes
- fs.c: implement vfs_lookup_parent (split path into parent dir + basename)
and all VFS mutation wrappers that resolve mount points transparently
- diskfs.c: implement VFS callback wrappers (diskfs_vfs_create, diskfs_vfs_mkdir,
diskfs_vfs_unlink, diskfs_vfs_rmdir, diskfs_vfs_rename, diskfs_vfs_truncate)
using parent diskfs_node ino for correct hierarchy scoping
- diskfs.c: wire callbacks into root and subdirectory nodes via diskfs_set_dir_ops
- syscall.c: open/mkdir/unlink/rmdir/rename now use generic VFS functions
instead of hardcoded path[0]==/ && path[1]==d... checks
- syscall.c: remove #include diskfs.h (only diskfs_link extern remains)
Any filesystem mounted via vfs_mount that implements these callbacks will
now transparently support file creation, directory operations, and rename
without requiring syscall.c modifications.
Tulio A M Mendes [Fri, 13 Feb 2026 00:29:02 +0000 (21:29 -0300)]
refactor: extract x86 kernel stack setup and register accessors from scheduler to arch layer
- New include/arch_process.h: arch-agnostic prototypes for arch_kstack_init(),
arch_regs_set_retval(), arch_regs_set_ustack()
- New src/arch/x86/arch_process.c: x86 implementation (EFLAGS 0x202, cdecl
stack frame layout matching context_switch in process.S)
- scheduler.c: process_create_kernel, process_fork_create, process_clone_create
now use arch_kstack_init() instead of inline x86 stack manipulation
- scheduler.c: process_clone_create uses arch_regs_set_retval/arch_regs_set_ustack
instead of direct .eax/.useresp access
No x86-specific constants or register names remain in scheduler.c.
Tulio A M Mendes [Thu, 12 Feb 2026 08:47:23 +0000 (05:47 -0300)]
docs: update all documentation for DOOM port, euid/egid, /dev/fb0, /dev/kbd, fd-backed mmap
README.md:
- Added DOOM port (/bin/doom.elf), /dev/fb0, /dev/kbd to features
- Updated permissions: euid/egid + VFS enforcement on open()
- Updated mmap: now includes fd-backed mappings
- Updated guard pages: kernel stacks at 0xC8000000
- Updated ulibc: added all new headers (stdlib.h, ctype.h, sys/mman.h, etc.)
- Updated POSIX score: 90% → 93%
- Removed file-backed mmap from remaining work (now implemented)
- Added user/doom/ to directory structure
BUILD_GUIDE.md:
- Added Section 3: Building DOOM (setup, build, run instructions)
- Updated ulibc description with all new headers
- Added doom.elf to initrd listing
Tulio A M Mendes [Thu, 12 Feb 2026 08:34:46 +0000 (05:34 -0300)]
feat: proper uid/gid + euid/egid implementation with permission enforcement
Kernel:
- struct process: added euid/egid (effective uid/gid) fields
- process_fork_create: now inherits uid/gid/euid/egid from parent
(previously left at 0 from memset)
- process_clone_create: also inherits euid/egid
- setuid/setgid: permission checks — only euid==0 can set arbitrary
uid/gid; unprivileged processes can only set to their real uid/gid
- New syscalls: geteuid (88), getegid (89), seteuid (90), setegid (91)
- vfs_check_permission(): checks owner/group/other rwx bits against
process euid/egid and file uid/gid/mode
- open() now calls vfs_check_permission() for R/W/RW access
- chmod: only root or file owner can change mode
- chown: only root can change ownership
- Added EACCES (13) to errno.h
Tulio A M Mendes [Thu, 12 Feb 2026 08:13:01 +0000 (05:13 -0300)]
feat: include doom.elf in initrd when built
The Makefile now conditionally includes user/doom/doom.elf in the
initrd as bin/doom.elf if it exists. This allows DOOM to be
launched from the AdrOS shell via: /bin/doom.elf -iwad /path/to/doom1.wad
The DOOM build is optional — the main kernel build is unaffected
if doomgeneric has not been cloned.
Tulio A M Mendes [Thu, 12 Feb 2026 07:40:52 +0000 (04:40 -0300)]
feat: guard pages for kernel stacks — detect overflow via page fault
Replaced kmalloc(4096) kernel stack allocation with a dedicated
kstack_alloc() that uses a virtual address region (0xC8000000+)
with guard pages. Each stack slot is 2 pages:
If a kernel stack overflows, the CPU hits the unmapped guard page
and triggers a page fault instead of silently corrupting heap
metadata. This eliminates the class of heap corruption bugs caused
by deep syscall call chains or large stack frames.
Added userspace wrappers required for the DOOM port:
- sys/mman.h + mman.c: mmap() and munmap() for framebuffer mapping
- sys/ioctl.h + ioctl.c: ioctl() for framebuffer info queries
- time.h + time.c: nanosleep() and clock_gettime() for frame timing
All wrappers use the existing INT 0x80 syscall interface.
Tulio A M Mendes [Thu, 12 Feb 2026 07:32:51 +0000 (04:32 -0300)]
feat: /dev/kbd raw scancode device for game input (DOOM)
Added raw scancode ring buffer to the keyboard driver. The HAL
keyboard layer now fires a second callback with the unprocessed
scancode byte (both key-press and key-release events).
- hal/keyboard.h: added hal_keyboard_scan_cb_t and setter
- hal/x86/keyboard.c: fires g_scan_cb before ASCII translation
- drivers/keyboard.c: raw scancode buffer + /dev/kbd device node
registered via devfs (non-blocking read returns raw scancodes)
- init.c: calls keyboard_register_devfs() after devfs is mounted
DOOM can now open /dev/kbd and read raw PS/2 scancodes to detect
key press/release events without TTY line buffering.
Tulio A M Mendes [Thu, 12 Feb 2026 07:26:30 +0000 (04:26 -0300)]
feat: /dev/fb0 framebuffer device + fd-backed mmap support
Added /dev/fb0 device node registered via devfs by vbe.c:
- ioctl: FBIOGET_VSCREENINFO (resolution, bpp), FBIOGET_FSCREENINFO
(phys addr, pitch, size)
- mmap: maps physical framebuffer into userspace with NOCACHE flags
- read/write: direct pixel buffer access via offset
Extended syscall_mmap_impl to support fd-backed mmap: when
MAP_ANONYMOUS is not set, the file descriptor's node->mmap callback
is invoked. This enables userspace to mmap /dev/fb0 for direct
framebuffer access (required for DOOM).
Marked syscall_mmap_impl as noinline to prevent GCC from merging it
into syscall_handler (4KB kernel stack limit).
Tulio A M Mendes [Thu, 12 Feb 2026 07:11:59 +0000 (04:11 -0300)]
refactor: add ioctl/mmap callbacks to fs_node_t, decouple ioctl dispatch
Added ioctl and mmap function pointers to fs_node_t for generic
device dispatch. Refactored syscall_ioctl_impl to call node->ioctl
instead of hardcoding TTY/PTY dispatch by inode number.
- tty.c: added tty_devfs_ioctl wrapper, set on console/tty nodes
- pty.c: added pty_slave_ioctl_fn wrapper, set on all slave nodes
- syscall.c: ioctl now dispatches generically through node callback
This prepares the VFS for /dev/fb0 and other devices that need
ioctl and mmap support.
Tulio A M Mendes [Thu, 12 Feb 2026 07:04:21 +0000 (04:04 -0300)]
refactor: extract generic VMM wrappers from x86 implementation to src/mm/vmm.c
Moved vmm_protect_range(), vmm_as_activate(), and vmm_as_map_page()
from src/arch/x86/vmm.c to src/mm/vmm.c. These functions contain only
architecture-independent logic (looping over pages, delegating to HAL
for address space switching).
The x86-specific VMM code (PAE page table manipulation, recursive
mapping, CoW handling, address space create/destroy/clone) remains
in src/arch/x86/vmm.c where it belongs.
This ensures new architectures only need to implement the core
primitives (vmm_init, vmm_map_page, vmm_unmap_page, vmm_set_page_flags,
vmm_as_create_kernel_clone, vmm_as_destroy, vmm_as_clone_user,
vmm_as_clone_user_cow, vmm_handle_cow_fault) and get the wrapper
functions for free.
Tulio A M Mendes [Thu, 12 Feb 2026 07:01:14 +0000 (04:01 -0300)]
refactor: decouple DevFS from TTY/PTY drivers via device registration API
Added devfs_register_device() API so device drivers register their own
fs_node_t with DevFS. DevFS is now a generic device registry that
dispatches through function pointers — it no longer includes tty.h or
pty.h and has zero knowledge of TTY/PTY internals.
- tty.c: registers /dev/console and /dev/tty with VFS-compatible wrappers
- pty.c: registers /dev/ptmx and /dev/pts (with finddir/readdir moved
from devfs.c)
- devfs.c: only owns built-in devices (null, zero, random, urandom);
all other devices come from the registry
This enables any future driver to register device nodes without
modifying devfs.c.
Tulio A M Mendes [Thu, 12 Feb 2026 06:46:40 +0000 (03:46 -0300)]
refactor: move lwIP port headers from src/net/lwip_port/ to include/net/
Moved lwipopts.h and arch/cc.h to include/net/ where they belong
alongside other public headers. Updated Makefile include path from
-Isrc/net/lwip_port to -Iinclude/net.
Also fixed cc.h to use arch-conditional BYTE_ORDER instead of
hardcoding x86 little-endian, supporting ARM, RISC-V, and MIPS
targets.
Tulio A M Mendes [Thu, 12 Feb 2026 06:37:06 +0000 (03:37 -0300)]
refactor: extract x86 CMOS I/O from drivers/rtc.c to HAL layer
Created include/hal/rtc.h with generic HAL RTC interface and
src/hal/x86/rtc.c with x86 CMOS port I/O implementation.
src/drivers/rtc.c is now fully architecture-agnostic: it calls
hal_rtc_read_raw() for hardware access and keeps only the generic
BCD-to-binary conversion and UNIX timestamp calculation logic.
This follows the same HAL pattern used by timer, keyboard, uart,
and video drivers.
Tulio A M Mendes [Thu, 12 Feb 2026 06:25:39 +0000 (03:25 -0300)]
fix: add timeout to UART busy-wait in hal_uart_putc()
The UART transmit loop now gives up after ~100k iterations instead
of spinning forever. This prevents the kernel from hanging with
the console spinlock held if the UART hardware is unresponsive,
which would otherwise deadlock all CPUs attempting kprintf (including
panic and debug output paths).
PID 0 previously used the boot stack from assembly (_stack_top),
which is not heap-managed. This caused two issues:
- TSS esp0 was not updated when switching to PID 0 (kernel_stack
was NULL, so the guard in schedule() skipped the update)
- If PID 0 were ever reaped or its stack freed, it would corrupt
memory since the boot stack is not a kmalloc'd block
Now process_init() allocates a 4KB kernel stack via kmalloc and
sets TSS esp0 to its top, matching the pattern used by all other
processes.
Tulio A M Mendes [Thu, 12 Feb 2026 06:21:08 +0000 (03:21 -0300)]
fix: save/restore EFLAGS in context_switch instead of forcing sti after schedule()
context_switch now uses pushf/popf to properly save and restore the
EFLAGS register (including the IF bit) across context switches.
This replaces the unconditional hal_cpu_enable_interrupts() call
after context_switch in schedule(), which broke the interrupt-state
semantics for callers that needed atomicity.
All process creation functions (fork, clone, kernel thread) now push
EFLAGS=0x202 (IF=1) onto the initial stack so new processes start
with interrupts enabled via popf in context_switch.
Kernel socket subsystem over lwIP TCP/UDP PCBs with ring-buffer RX,
wait queues for blocking ops, and fd integration via sentinel file
structs (flags=0x534F434B).
Socket dispatch extracted to separate noinline function to prevent
syscall_handler stack overflow that caused heap corruption.
- include/waitqueue.h: reusable waitqueue_t with wq_init/push/pop/wake_one/wake_all
- src/kernel/tty.c: refactored to use waitqueue_t instead of ad-hoc arrays
- src/kernel/pty.c: refactored to use waitqueue_t instead of ad-hoc arrays
- Removed duplicate waitq_push/waitq_pop/waitq_empty/waitq_wake_one from both files
- cppcheck clean, 19/19 smoke tests pass
Tulio A M Mendes [Tue, 10 Feb 2026 12:55:26 +0000 (09:55 -0300)]
feat: multiple PTY pairs (up to 8 dynamic /dev/pts/N)
Refactored PTY subsystem from single global pair to array of up to 8 pairs:
- New pty_pair struct with per-pair buffers, waitqueues, session/pgrp
- pty_alloc_pair() allocates new pairs dynamically
- Inode encoding: masters=100+N, slaves=200+N
- pty_get_master_node()/pty_get_slave_node() return per-pair fs_node_t
- All _idx() variants for indexed pair access
- Old single-pair API preserved as wrappers around pair 0
- devfs /dev/pts/ now lists all active pairs dynamically
- syscall.c poll/nonblock/ioctl updated to use pty_is_master_ino/pty_is_slave_ino
- cppcheck clean, 19/19 smoke tests pass
Tulio A M Mendes [Tue, 10 Feb 2026 11:38:03 +0000 (08:38 -0300)]
fix: 5 HIGH severity bugs from audit
2.3: Slab allocator now uses kmalloc(PAGE_SIZE) instead of
pmm_alloc_page + hal_mm_phys_to_virt. The old approach could
map physical addresses above 16MB to VAs that collide with the
heap range (0xD0000000+), causing silent memory corruption.
3.3: execve now validates sp against stack base before each write.
Prevents writing below the user stack page if E2BIG pre-check
is somehow bypassed. Returns -E2BIG on underflow.
3.4: SMEP (Supervisor Mode Execution Prevention) enabled in CR4
if CPU supports it. Prevents kernel from executing user-mapped
pages, blocking a common exploit technique. SMAP detection added
but not enabled yet (requires STAC/CLAC in uaccess.c first).
CPUID leaf 7 detection added for SMEP (bit 7) and SMAP (bit 20).
4.1: Kernel heap now grows dynamically from 10MB up to 64MB max.
When kmalloc can't find a free block, kheap_grow maps new
physical pages at the end of the heap and creates a new free
block. Coalesces with tail if adjacent and free.
2.4: process_waitpid circular list traversal now checks for NULL
before comparing to start, preventing NULL deref if the list
is broken by concurrent reaping.
Tulio A M Mendes [Tue, 10 Feb 2026 11:29:36 +0000 (08:29 -0300)]
fix: 4 CRITICAL security/race bugs from audit
3.1: user_range_ok weak default now rejects kernel addresses (>= 0xC0000000)
Prevents privilege escalation via syscall arguments on non-x86 fallback.
3.2: sigreturn sanitizes eflags — clears IOPL bits, ensures IF set.
Prevents userspace from gaining port I/O access via crafted sigframe.
2.1: PMM bitmap/refcount now protected by spinlock_t pmm_lock.
Prevents SMP race where two CPUs could allocate the same physical frame.
All public PMM functions (alloc, free, mark_region, incref, decref,
get_refcount) now use spin_lock_irqsave/spin_unlock_irqrestore.
2.2: file->refcount now uses __sync_fetch_and_add / __sync_sub_and_fetch.
Prevents use-after-free in fork/dup/dup2/dup3/close when timer IRQ
fires and schedule() runs process_close_all_files_locked concurrently.
Tulio A M Mendes [Tue, 10 Feb 2026 11:07:09 +0000 (08:07 -0300)]
feat: deep code audit + testing infrastructure (sparse, expect, host unit tests)
Deep Code Audit (docs/AUDIT_REPORT.md):
- 18 findings across 4 categories: layer violations, logic/race
conditions, security vulnerabilities, memory management
- CRITICAL: user_range_ok weak default allows kernel addr access
- CRITICAL: sigreturn allows IOPL escalation via eflags
- CRITICAL: PMM bitmap has no locking (SMP race)
- CRITICAL: file refcount manipulation not atomic
- HIGH: slab allocator hal_mm_phys_to_virt can hit heap VA
- HIGH: execve writes to user stack bypassing copy_to_user
- Full summary table with severity, category, location
Testing Infrastructure:
- make check — cppcheck + sparse (kernel-oriented semantic checker)
- make analyzer — gcc -fanalyzer (interprocedural analysis)
- make test — QEMU + expect automated smoke test (19 checks)
- make test-1cpu — single-CPU regression (50s timeout)
- make test-host — 28 host-side unit tests for pure functions
(itoa, itoa_hex, atoi, path_normalize, align)
- make test-all — all of the above
Testing Plan (docs/TESTING_PLAN.md):
- Layer 1: Static analysis (cppcheck + sparse + gcc -fanalyzer)
- Layer 2: QEMU + expect automated regression
- Layer 3: QEMU + GDB scripted debugging (future)
- Layer 4: Host-side unit tests for pure functions
All tests passing: 19/19 smoke, 28/28 unit, cppcheck clean.
Tulio A M Mendes [Tue, 10 Feb 2026 10:37:38 +0000 (07:37 -0300)]
feat: Fase 9 — ATA Bus Master IDE DMA for read/write
Implement Bus Master IDE DMA as a transparent upgrade over PIO:
- New ata_dma.c: Bus Master IDE DMA driver for PIIX3 IDE controller
- Finds IDE controller via PCI class 0x01:0x01
- Reads BAR4 for Bus Master I/O base, enables PCI bus mastering
- Allocates PRDT and bounce buffer pages at dedicated VAs
(0xC0220000/0xC0221000) to avoid heap VA collisions
- Polling-based DMA completion (BSY clear + BM Active clear)
- IRQ 14 handler with dma_active flag to prevent race between
IRQ handler and polling loop on ATA status register
- spin_lock (not irqsave) for serialization — PIIX3 requires
interrupt delivery for DMA completion signaling
- Modified ata_pio.c: transparent DMA upgrade
- ata_pio_init_primary_master registers IRQ 14 handler early
(before IDENTIFY) to prevent INTRQ storm
- Calls ata_dma_init after IDENTIFY to probe for DMA capability
- ata_pio_read28/write28 delegate to DMA when available,
fall back to PIO if DMA init failed
- New include/ata_dma.h: public API header
Key bugs fixed during development:
- hal_mm_phys_to_virt can map PMM pages into heap VA range
(phys 0x10000000 -> virt 0xD0000000 = heap base) — use
dedicated VAs instead
- ATA INTRQ must be deasserted by reading status register;
without IRQ handler, unacknowledged INTRQ causes interrupt storm
- nIEN must be cleared before DMA so device asserts INTRQ
- DMA direction bit must be set before Start bit per ATA spec
Tested: 4-CPU (25s) and 1-CPU (45s) pass all init.elf tests
including diskfs getdents and /persist/counter.
Tulio A M Mendes [Tue, 10 Feb 2026 09:26:46 +0000 (06:26 -0300)]
feat: Fase 8a — Per-CPU data infrastructure with GS-segment access
New files:
- include/arch/x86/percpu.h — Per-CPU data structure and GS-based
accessors (percpu_get, percpu_cpu_index, percpu_current, etc.)
- src/arch/x86/percpu.c — Per-CPU init: creates GDT entries for each
CPU's GS segment pointing to its percpu_data instance
Changes:
- include/arch/x86/smp.h: Split smp_init into smp_enumerate() and
smp_start_aps() to allow percpu_init between enumeration and SIPI
- src/arch/x86/smp.c: Implement two-phase SMP init
- include/arch/x86/gdt.h: Export gdt_ptr struct, gp variable, and
gdt_set_gate_ext() for per-CPU GDT entry creation
- src/arch/x86/gdt.c: Expand GDT from 6 to 24 entries (6 base +
up to 16 per-CPU GS segments). Add gdt_set_gate_ext(). Make gp
non-static.
- src/arch/x86/arch_platform.c: Call smp_enumerate() -> percpu_init()
-> percpu_setup_gs(0) -> smp_start_aps() in correct order
Boot sequence for per-CPU setup:
1. smp_enumerate() — populate cpu_info from ACPI MADT
2. percpu_init() — create GDT entries for each CPU's GS segment
3. percpu_setup_gs(0) — BSP loads its own GS selector
4. smp_start_aps() — send INIT-SIPI-SIPI; each AP calls
percpu_setup_gs(i) during its init
Passes: make, cppcheck, QEMU smoke test (-smp 1 and -smp 4)
Tulio A M Mendes [Tue, 10 Feb 2026 09:16:11 +0000 (06:16 -0300)]
feat: Fase 7 — ACPI MADT parser + SMP AP bootstrap (INIT-SIPI-SIPI)
New files:
- include/arch/x86/acpi.h — ACPI RSDP/RSDT/MADT structures and API
- include/arch/x86/smp.h — SMP per-CPU info and bootstrap API
- src/arch/x86/acpi.c — ACPI table parser: find RSDP in EBDA/BIOS ROM,
parse RSDT, extract MADT entries (LAPIC, IOAPIC, ISO). Uses temporary
VMM mappings for tables above the 16MB identity-mapped range.
- src/arch/x86/smp.c — SMP bootstrap: copy 16-bit trampoline to 0x8000,
patch GDT/CR3/stack/entry, send INIT-SIPI-SIPI per AP, wait for ready.
- src/arch/x86/ap_trampoline.S — 16-bit real-mode AP entry point:
load GDT, enable protected mode, far-jump to 32-bit, load CR3,
enable paging, jump to C ap_entry().
Changes:
- include/arch/x86/lapic.h: Add lapic_send_ipi(), rdmsr(), wrmsr() decls
- src/arch/x86/lapic.c: Implement lapic_send_ipi() using ICR_HI/ICR_LO
with delivery-status polling. Make rdmsr/wrmsr non-static.
- include/arch/x86/gdt.h: Export struct gdt_ptr and gp variable
- src/arch/x86/gdt.c: Make gp non-static for AP trampoline access
- src/arch/x86/linker.ld: Include .ap_trampoline section in .rodata
- src/arch/x86/arch_platform.c: Call acpi_init() then smp_init() after
LAPIC/IOAPIC setup
- src/arch/x86/idt.c: Move IRQ EOI before handler callback (critical fix:
schedule() in timer handler context-switches away, blocking LAPIC if
EOI is deferred). Add IDT gate + ISR stub for spurious vector 255.
- src/arch/x86/interrupts.S: Add ISR_NOERRCODE 255 stub
Key design decisions:
- Trampoline at fixed phys 0x8000, data area at 0x8F00
- APs share BSP's page directory (same CR3)
- APs enable their own LAPIC and halt (idle loop for now)
- ACPI tables mapped via temporary VMM window at 0xC0202000
- Each AP gets a 4KB kernel stack from static array
Tested: -smp 1 (single CPU, all init tests OK)
-smp 4 (4 CPUs started, all init tests OK)
Passes: make, cppcheck, QEMU smoke test
New files:
- include/arch/x86/lapic.h — LAPIC register definitions and API
- include/arch/x86/ioapic.h — IOAPIC register definitions and API
- src/arch/x86/lapic.c — Local APIC driver: init, EOI, MMIO access,
timer calibration via PIT channel 2, pic_disable()
- src/arch/x86/ioapic.c — I/O APIC driver: init, IRQ routing,
mask/unmask per-IRQ line
Changes:
- vmm.h: Add VMM_FLAG_PWT, VMM_FLAG_PCD, VMM_FLAG_NOCACHE for MMIO
- vmm.c: Translate PWT/PCD flags to x86 PTE bits in vmm_flags_to_x86
- arch_platform.c: Init LAPIC+IOAPIC after syscall_init, route ISA
IRQs (timer=32, kbd=33, ATA=46) through IOAPIC, disable PIC only
after IOAPIC routes are live
- idt.c: Send EOI BEFORE handler callback (critical: schedule() in
timer handler context-switches away; deferred EOI blocks LAPIC).
Add IDT gate for spurious vector 255; skip EOI for spurious
interrupts per Intel spec.
- interrupts.S: Add ISR stub for vector 255 (LAPIC spurious)
- timer.c: Use LAPIC periodic timer when available, fallback to PIT
Key design decisions:
- LAPIC MMIO mapped at 0xC0200000 (above kernel _end, below heap)
- IOAPIC MMIO mapped at 0xC0201000
- Both mapped with PCD+PWT (cache-disable) to prevent MMIO caching
- PIC disabled only AFTER IOAPIC routes configured (avoids IRQ gap)
- EOI sent before handler to prevent LAPIC starvation on context switch
- Spurious vector 255 has IDT entry but no EOI (Intel requirement)
- LAPIC timer calibrated against PIT channel 2 (~10ms measurement)
Bugs fixed during development:
- VA 0xC0100000 overlapped kernel text — moved to 0xC0200000
- pic_disable() inside lapic_init() caused IRQ gap — moved to caller
- EOI after handler blocked LAPIC when schedule() context-switched
- Missing IDT entry for vector 255 caused triple fault on spurious IRQ
Passes: make, cppcheck, QEMU smoke test (all init tests OK).
Tulio A M Mendes [Tue, 10 Feb 2026 08:13:16 +0000 (05:13 -0300)]
fix: audit and correct bugs in Fases 1-4
Fase 1 (CPUID):
- cpuid.c: Replace strict-aliasing-violating uint32_t* casts for
vendor string with memcpy() to avoid UB
- cpuid.c: Increase itoa tmp buffer from 4 to 12 bytes to prevent
potential overflow with larger APIC IDs
Fase 2 (Spinlock): No bugs found — TTAS + cpu_relax + barriers OK
Fase 3 (SYSENTER):
- sysenter.S: Add 'cld' at entry to clear direction flag. Userspace
could leave DF=1 and SYSENTER doesn't reset EFLAGS, which would
corrupt any kernel string/memory operations using rep movsb/stosb
Fase 4 (ulibc):
- stdio.c: Fix PUTC macro underflow — use 'pos+1 < size' instead
of 'pos < size-1' which underflows to SIZE_MAX when size==0
- stdio.c: Fix vsnprintf(buf, 0, fmt) — was counting raw format
chars instead of returning 0; now returns 0 immediately
- stdlib.c: Use uintptr_t instead of unsigned int for brk() pointer
comparison to be correct on all architectures
- unistd.c: Replace 'hlt' with 'nop' in _exit() fallback loop —
hlt is privileged and causes #GP in ring 3
Bugs found and fixed during deep audit of the Fase 5 commit
(implemented during WSL2/GCC instability):
BUG 1 (CRITICAL): vmm_map_page args were inverted in shm_at().
Signature is vmm_map_page(phys, virt, flags) but code passed
(virt, phys, flags). Would map physical pages at wrong addresses
causing memory corruption. Fixed both code paths.
BUG 2 (CRITICAL): shm_dt() used broken heuristic to find segment.
Matched by npages count — if two segments had same page count,
wrong one got decremented. Added shmid field to mmap entry struct
for direct O(1) lookup. Removed dead code loop that computed
expected_va and discarded it.
BUG 3: shm_at() with shmaddr!=0 didn't register in mmaps[].
shm_dt() would never find the slot, returning -EINVAL.
Now always registers in mmap table regardless of shmaddr.
BUG 4: shm_destroy() only cleared 'used' flag, leaving stale
key/size/npages/nattch. Now memset()s entire struct to zero.
BUG 5: shm_ctl(IPC_STAT) wrote directly to userspace pointer
while holding spinlock. Page fault under spinlock = deadlock.
Now copies to local struct, releases lock, then copy_to_user().
Additional fixes:
- Added shmid field to process mmap entry (process.h)
- Initialize mmaps[].shmid = -1 in all 3 process creation paths
(process_init, process_create_kernel, process_fork_create)
- Set shmid = -1 in syscall_mmap_impl and syscall_munmap_impl
- Fork now copies parent's mmap table to child (with shmid)
Passes: make, cppcheck, QEMU smoke test (all init tests OK).
Add System V-style shared memory IPC subsystem:
- src/kernel/shm.c: kernel-side segment manager with up to 32
segments, each up to 16 pages (64KB). Physical pages allocated
via PMM, mapped into user address space via VMM.
- include/shm.h: API + constants (IPC_CREAT, IPC_EXCL, IPC_RMID,
IPC_PRIVATE)
- Syscalls 46-49: SHMGET, SHMAT, SHMDT, SHMCTL wired in syscall.c
- shm_init() called from kernel_main after kheap_init
- Deferred destruction: IPC_RMID with nattch>0 defers free until
last detach
Also fixes:
- tty.c: add utils.h include for memset (cross-compiler strictness)
- usermode.c: fix ebp clobber error with cross-compiler by using
ESI as scratch register instead
Tulio A M Mendes [Tue, 10 Feb 2026 07:07:14 +0000 (04:07 -0300)]
feat: implement SYSENTER/SYSEXIT fast syscall entry for x86-32
Add fast syscall support via SYSENTER/SYSEXIT (SEP), ~10x faster
than INT 0x80 (~30 cycles vs ~300 cycles overhead).
Components:
- src/arch/x86/sysenter.S: assembly entry point that builds a
struct registers frame compatible with existing syscall_handler()
- src/arch/x86/sysenter_init.c: MSR setup (0x174=CS, 0x175=ESP,
0x176=EIP), CPUID check for SEP support
- syscall_handler() made non-static so assembly can call it
- tss_set_kernel_stack() now also updates SYSENTER ESP MSR so
context switches keep the fast path working
HAL wrapper (include/hal/cpu_features.h):
- hal_cpu_detect_features() / hal_cpu_get_features() / hal_cpu_print_features()
- x86 impl in src/hal/x86/cpu_features.c
- Weak default stub in src/kernel/cpu_features.c
Called from kernel_main() right after console_init().
QEMU output: GenuineIntel, PAE APIC SEP SSE SSE2 FXSR HYPERVISOR.
Tulio A M Mendes [Tue, 10 Feb 2026 06:27:16 +0000 (03:27 -0300)]
refactor: remove dead shell.c, integrate commands into kconsole
shell.c became dead code after kconsole_enter() replaced shell_init()
as the fallback when init_start() fails. Nobody called shell_init().
- Integrate useful shell commands into kconsole: ls, cat, clear,
mem, sleep, ring3 (via arch_platform_usermode_test_start)
- Remove src/kernel/shell.c and include/shell.h
- Remove shell.h include from main.c
- kconsole is now the single kernel-mode interactive console
Tulio A M Mendes [Tue, 10 Feb 2026 06:19:36 +0000 (03:19 -0300)]
refactor: move x86 uaccess page table walking to src/arch/x86/uaccess.c
The uaccess implementation used x86-specific recursive page table
mapping (0xFFFFF000/0xFFC00000) for user_range_ok, copy_from_user,
copy_to_user, and uaccess_try_recover. This is 100% arch-dependent.
- Move full x86 implementation to src/arch/x86/uaccess.c (no guards)
- Replace src/kernel/uaccess.c with weak stubs (simple memcpy-based)
- The linker picks the arch-specific version when available
Tulio A M Mendes [Tue, 10 Feb 2026 06:13:16 +0000 (03:13 -0300)]
refactor: make pmm.c fully architecture-independent
Extract all Multiboot2 x86-specific code from src/mm/pmm.c into
src/arch/x86/pmm_boot.c as pmm_arch_init().
Design:
- pmm.h now exposes pmm_mark_region(), pmm_set_limits(), pmm_arch_init()
- pmm_init() calls pmm_arch_init() (arch-specific) which discovers
memory and calls pmm_set_limits() + pmm_mark_region()
- pmm.c provides a weak default pmm_arch_init() for archs without one
- Kernel protection uses hal_mm_virt_to_phys() (no #if MIPS/x86)
- x86 pmm_boot.c handles Multiboot2 parsing, module protection,
and boot info protection
- Zero #if guards remain in pmm.c
When VFS mount or init fails, AdrOS now enters a minimal kernel-mode
emergency console instead of the full shell. This is similar to
HelenOS's kconsole — it runs entirely in kernel mode and provides
basic diagnostic commands.
Design:
- kconsole_enter() runs a blocking read loop using kgetc()/kprintf()
- Commands: help, dmesg, reboot, halt
- Activated from kernel_main() when init_start() returns < 0
- Fully architecture-independent (uses only generic console API)
The normal shell remains available for userspace-initiated sessions.
Tulio A M Mendes [Tue, 10 Feb 2026 05:56:54 +0000 (02:56 -0300)]
feat: add kgetc() to console subsystem for kernel input
kgetc() reads a single character via keyboard_read_blocking(),
providing a symmetric API alongside kprintf() for output.
This enables the kernel console (and future kconsole) to have
a generic input mechanism independent of architecture.
Tulio A M Mendes [Tue, 10 Feb 2026 05:53:40 +0000 (02:53 -0300)]
refactor: remove #if x86 guard from scheduler fork_child_trampoline
Add hal_usermode_enter_regs() to the HAL usermode interface so
fork_child_trampoline() can call it generically without #if x86.
- Add declaration to include/hal/usermode.h
- Implement in src/hal/x86/usermode.c (calls x86_enter_usermode_regs)
- Add stubs in ARM, RISC-V, MIPS HAL usermode.c
- scheduler.c now uses hal_usermode_enter_regs() with no guards
Tulio A M Mendes [Tue, 10 Feb 2026 05:47:20 +0000 (02:47 -0300)]
refactor: remove all #if x86 guards from shell.c, migrate to kprintf + HAL
shell.c is generic kernel code and should not contain any
architecture-specific guards or inline assembly.
Changes:
- Replace all uart_print() calls with kprintf() so output goes
through the console subsystem and into the kernel log buffer
- Replace x86 'cli; ud2' panic asm with hal_cpu_disable_interrupts()
+ hal_cpu_idle() loop
- Replace #if x86 ring3 guard with arch_platform_usermode_test_start()
which is already a generic wrapper with per-arch implementations
- Use console_write() for echo/backspace (no log buffer pollution)
- Remove unnecessary uart_console.h and vga_console.h includes
Tulio A M Mendes [Tue, 10 Feb 2026 05:42:24 +0000 (02:42 -0300)]
refactor: move x86 ATA PIO driver from src/drivers/ata_pio.c to src/hal/x86/ata_pio.c
ATA PIO uses x86 I/O port instructions (inb/outb/inw/outw) for
disk access. The full implementation now lives in src/hal/x86/ata_pio.c
(no #if guards). src/drivers/ata_pio.c contains only weak stubs
returning -ENOSYS, overridden at link time for x86.
Tulio A M Mendes [Tue, 10 Feb 2026 05:37:31 +0000 (02:37 -0300)]
refactor: move x86 PCI driver from src/drivers/pci.c to src/hal/x86/pci.c
PCI config space access via I/O ports 0xCF8/0xCFC is x86-specific.
The full PCI enumeration implementation now lives in src/hal/x86/pci.c
(no #if guards). src/drivers/pci.c contains only weak stubs that are
overridden by the arch-specific version at link time.
Tulio A M Mendes [Tue, 10 Feb 2026 05:32:41 +0000 (02:32 -0300)]
refactor: move x86 ELF loader from src/kernel/elf.c to src/arch/x86/elf.c
The ELF loader uses x86-specific page table manipulation (recursive
mapping at 0xFFFFF000/0xFFC00000), EM_386 validation, and low-16MB
allocation. It is 100% architecture-dependent and should not live
in the generic kernel directory.
- Move full implementation to src/arch/x86/elf.c (no #if guards)
- Replace src/kernel/elf.c with a weak stub returning -1
- The linker picks the arch-specific version when available
Tulio A M Mendes [Tue, 10 Feb 2026 05:26:38 +0000 (02:26 -0300)]
refactor: remove include/multiboot2.h wrapper, use arch/x86/multiboot2.h directly
The generic wrapper include/multiboot2.h only forwarded to
arch/x86/multiboot2.h under #if x86. Since Multiboot2 is purely
x86/GRUB-specific, the wrapper should not exist in the generic
include/ directory. pmm.c now includes arch/x86/multiboot2.h
directly (already inside its own #if x86 guard).