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.
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.
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.
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
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)
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.
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.
- 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).
- 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.
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.
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.
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
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.
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
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.
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)
Tulio A M Mendes [Fri, 13 Feb 2026 21:32:46 +0000 (18:32 -0300)]
feat: expand c_cc[] with POSIX control character indices
- NCCS expanded from 8 to 11
- Define VINTR(0), VQUIT(1), VERASE(2), VKILL(3), VEOF(4),
VSUSP(7), VMIN(8), VTIME(9) with standard index values
- Initialize tty_cc[] with POSIX defaults:
VINTR=^C, VQUIT=^\, VERASE=DEL, VKILL=^U, VEOF=^D, VSUSP=^Z
- Replace all hardcoded signal/control character comparisons in
tty_input_char with tty_cc[] lookups
- VERASE now accepts both 0x08 (BS) and 0x7F (DEL)
- All c_cc[] entries are user-configurable via TCSETS
VFS dispatch (fs.c + syscall.c) checks f_ops first, falls back to
legacy per-node pointers. Legacy pointers are still set (dual
assignment) for callers that access them directly (e.g. overlayfs
layer delegation). Phase B3 will remove legacy pointers after all
direct accesses are eliminated.
Tulio A M Mendes [Fri, 13 Feb 2026 21:05:14 +0000 (18:05 -0300)]
refactor: VFS file_operations dispatch layer
Add struct file_operations to fs.h with all VFS callback signatures.
Add const struct file_operations* f_ops to fs_node_t.
Update all VFS dispatch points (fs.c wrappers + syscall.c direct
dispatch for poll, readdir, ioctl, mmap) to check f_ops first,
then fall back to legacy per-node function pointers.
This enables incremental migration: filesystems can adopt f_ops
one at a time while legacy pointers continue to work.
Tulio A M Mendes [Fri, 13 Feb 2026 21:00:07 +0000 (18:00 -0300)]
feat: O(1) sorted sleep queue for process_wake_check
Replace O(N) scan of all processes with a sorted doubly-linked sleep
queue. process_wake_check now pops expired entries from the queue head
in O(1) time. The O(N) scan is retained only for alarm delivery.
Key design decisions:
- sleep_prev/sleep_next/in_sleep_queue fields added to struct process
- process_sleep() inserts into sorted queue under sched_lock
- schedule() handles deferred insertion for ksem_wait_timeout/futex
(SLEEPING set under external lock, inserted under sched_lock in
schedule — no preemption window)
- All wake paths (signal, kill, reap, sched_enqueue_ready) call
sleep_queue_remove to prevent double-insert corruption
- Defensive sleep_queue_remove before insert in process_sleep
Tulio A M Mendes [Fri, 13 Feb 2026 19:48:51 +0000 (16:48 -0300)]
refactor: move syscall_init arch dispatch to arch/x86/sysenter_init.c
- Add arch_syscall_init() that registers INT 0x80 handler and calls x86_sysenter_init()
- syscall_init() now just calls arch_syscall_init() — zero #ifdef in syscall.c
- x86_sysenter_init() made static (internal to sysenter_init.c)
- syscall.c contains ZERO architecture-specific code or #ifdefs
Tulio A M Mendes [Fri, 13 Feb 2026 18:45:18 +0000 (15:45 -0300)]
refactor: replace socket magic 0x534F434B with proper VFS FS_SOCKET nodes
- Add FS_SOCKET type to fs.h
- Create sock_node_create/close/read/write: proper fs_node_t for sockets
with read→ksocket_recv, write→ksocket_send, close→ksocket_close
- Socket ID stored in node->inode (previously in file->offset)
- sock_fd_get_sid helper validates socket FDs via FS_SOCKET type check
- socket()/accept() now create VFS nodes instead of magic-flagged files
- fd_close no longer needs special socket magic check
- read()/write() on socket FDs now work via standard VFS dispatch
- All 0x534F434BU magic references eliminated from codebase
E1000 networking overhaul — replace polling with proper interrupt-driven I/O:
1. RX interrupt-driven:
- IRQ handler (e1000_irq_handler) now signals e1000_rx_sem on
RXT0/RXDMT0/RXO events instead of being a no-op.
- Dedicated kernel thread (e1000_rx_thread) blocks on the
semaphore, drains all available packets via e1000_recv(),
and delivers them to lwIP via tcpip_input().
- Latency: immediate wake on packet arrival (was 20ms polling).
2. TX non-blocking:
- e1000_send() checks the DD bit immediately and returns -1 if
the descriptor is not ready (was: busy-wait up to 100K iters).
- lwIP's linkoutput callback returns ERR_IF on ring-full.
3. Idle loop cleanup:
- net_poll() removed from kernel_main's idle loop.
- net_poll() is now a no-op (kept for backward compat).
- PID 0 idle loop is pure hlt — no wasted CPU cycles.
4. root= kernel command line parameter:
- Syntax: root=/dev/hdX (e.g. root=/dev/hda)
- Auto-detects filesystem (tries diskfs, fat, ext2 in order)
- Mounts at /disk on success
- Processed after ATA init, before /etc/fstab parsing
- Example GRUB entry:
multiboot2 /boot/adros-x86.bin root=/dev/hda quiet
Tulio A M Mendes [Fri, 13 Feb 2026 09:43:51 +0000 (06:43 -0300)]
fix: hold sched_lock through context_switch to prevent timer race
Root cause of rare kernel panics with EIP on the kernel stack:
When schedule() was called from process context (waitpid, sleep),
irq_flags had IF=1. spin_unlock_irqrestore() re-enabled interrupts
BEFORE context_switch(). If a timer fired in this window:
1. current_process was already set to 'next' (line 835)
2. But we were still executing on prev's stack
3. Nested schedule() treated 'next' as prev, saved prev's ESP
into next->sp — CORRUPTING next->sp
4. Future context_switch to 'next' loaded the wrong stack offset,
popping garbage registers and a garbage return address
5. EIP ended up pointing into the kernel stack → PAGE FAULT
Fix (three parts):
1. schedule(): move context_switch BEFORE spin_unlock_irqrestore.
After context_switch we are on the new process's stack, and its
saved irq_flags correctly releases the lock.
2. arch_kstack_init: set initial EFLAGS to 0x002 (IF=0) instead of
0x202 so popf in context_switch doesn't enable interrupts while
the lock is held.
3. thread_wrapper: release sched_lock and enable interrupts, since
new processes arrive here via context_switch's ret (bypassing
the spin_unlock_irqrestore after context_switch).
Also: remove get_next_ready_process() which incorrectly returned
fallback processes not in rq_active, causing rq_dequeue to corrupt
the runqueue bitmap. Inlined the logic correctly in schedule().
Verified: 20/20 boots without 'ring3' — zero panics.
Build: clean, cppcheck: clean, smoke: 19/19 pass
Tulio A M Mendes [Fri, 13 Feb 2026 09:15:09 +0000 (06:15 -0300)]
feat: Linux-like kernel command line parser with /proc/cmdline
Implement a proper kernel command line parsing system modeled after
Linux's cmdline triaging:
1. Kernel params: recognized 'key=value' tokens (init=, root=,
console=, loglevel=) are consumed by the kernel.
2. Kernel flags: recognized plain tokens (quiet, ring3, nokaslr,
single, noapic, nosmp) are consumed by the kernel.
3. Init envp: unrecognized 'key=value' tokens become environment
variables for the init process.
4. Init argv: unrecognized plain tokens (no '=' or '.') become
command-line arguments for the init process.
5. '--' separator: everything after it goes to init untouched.
6. First token (kernel path) is always skipped.
New files:
- include/kernel/cmdline.h: API (cmdline_parse, cmdline_get,
cmdline_has, cmdline_init_path, cmdline_init_argv/envp, cmdline_raw)
- src/kernel/cmdline.c: implementation with static storage
Changes:
- init.c: calls cmdline_parse() early, uses cmdline_has('ring3')
instead of the old cmdline_has_token() (removed)
- arch_platform.c: uses cmdline_init_path() for init binary path
(supports 'init=/path/to/init' from GRUB cmdline)
- procfs.c: added /proc/cmdline file (readable by userspace)
The 'ring3' parameter is no longer required for stable boot (the
scheduler bug causing panics without it was fixed in the previous
commit). It now only controls the inline ring3 test.
Tulio A M Mendes [Fri, 13 Feb 2026 09:00:13 +0000 (06:00 -0300)]
fix: remove killed READY processes from runqueue before marking ZOMBIE
Root cause of intermittent kernel panic (PAGE FAULT at 0x0, ESP=0):
When process_kill(SIGKILL) killed a READY process (sitting in
rq_active or rq_expired), it set state=ZOMBIE but did NOT remove
the process from the runqueue. Later, the parent reaped the ZOMBIE
via waitpid → process_reap_locked → kfree(p), freeing the struct.
But the freed pointer remained in the runqueue. rq_pick_next()
returned the dangling pointer, schedule() read sp=0 from freed
heap memory, and context_switch loaded ESP=0 → PAGE FAULT.
The 'ring3' cmdline flag masked this bug by changing scheduler
timing: with ring3, the BSP entered usermode immediately via iret,
altering the sequence of context switches such that the ZOMBIE was
typically dequeued before being reaped.
Fix:
- Add rq_remove_if_queued() helper: safely searches both rq_active
and rq_expired for a process at its priority level before calling
rq_dequeue()
- process_kill(SIGKILL): dequeue READY victims before setting ZOMBIE
- process_reap_locked(): dequeue as safety net before freeing
Verified: 10/10 boots without 'ring3' — zero panics (was ~50% fail).
Build: clean, cppcheck: clean, smoke: 19/19 pass
Tulio A M Mendes [Fri, 13 Feb 2026 08:22:35 +0000 (05:22 -0300)]
fix: add IOAPIC route for IRQ 15 (secondary ATA channel)
The secondary ATA channel (IRQ 15, vector 47) was not routed through
the IOAPIC. After the multi-drive ATA refactor, ata_pio_init() probes
the secondary channel, which can generate IRQ 15 (e.g. IDENTIFY to
QEMU's ATAPI CD-ROM). Without a proper IOAPIC route:
1. The interrupt was lost (PIC disabled, IOAPIC not routing it)
2. The IOAPIC pin 15 remained in an undefined state
3. Depending on timing, this could cause spurious behavior
This was the likely root cause of intermittent kernel panics/reboots
when booting without the 'ring3' cmdline flag — the timing difference
meant the secondary ATA probe's unhandled IRQ could manifest as an
unrecoverable interrupt state.
Tulio A M Mendes [Fri, 13 Feb 2026 08:07:10 +0000 (05:07 -0300)]
feat: multi-drive ATA support (4 drives) + fstab/mount command
Major ATA driver refactoring:
1. ATA PIO driver (ata_pio.h, hal/x86/ata_pio.c):
- Support all 4 ATA drives: primary/master (hda), primary/slave (hdb),
secondary/master (hdc), secondary/slave (hdd)
- New API: ata_pio_init() probes both channels, ata_pio_read28/write28
take a drive ID parameter, ata_pio_drive_present() query
- ata_name_to_drive/ata_drive_to_name helpers for device name mapping
- Per-channel I/O ports (0x1F0/0x3F6 primary, 0x170/0x376 secondary)
- Master/slave selection (0xE0/0xF0 LBA mode bits)
- Floating bus detection (0xFF = no controller)
- ATAPI rejection (non-zero LBA1/LBA2 after IDENTIFY)
2. ATA DMA driver (ata_dma.h, hal/x86/ata_dma.c):
- Per-channel DMA state: separate PRDT, bounce buffer, spinlock, IRQ
handler for primary (BAR4+0) and secondary (BAR4+8)
- KVA map extended: 4 pages (PRDT+buf x 2 channels) at 0xC0320000-3
- PCI Bus Master probe factored out and cached (single PCI lookup)
- DMA-aware IRQ handlers for both IRQ 14 and IRQ 15
3. Filesystem drivers updated to accept drive parameter:
- fat_mount(int drive, uint32_t lba) — stores drive in g_fat struct
- ext2_mount(int drive, uint32_t lba) — stores drive in g_ext2 struct
- diskfs_create_root(int drive) — static g_diskfs_drive for all I/O
- persistfs_create_root(int drive) — delegates to diskfs on same drive
4. Mount system redesigned — no more auto-mount of disk FS:
- init.c: calls ata_pio_init() once, then parses /etc/fstab
- Fstab format: /dev/hdX /mountpoint fstype options
- init_mount_fs() helper reusable by both fstab and kconsole
- rootfs/etc/fstab created with default diskfs+persistfs on hda
5. kconsole mount command:
- 'mount -t <type> /dev/<hd> <mountpoint>' for manual mounting
- 'lsblk' lists all 4 ATA drives and their detection status
- Help text updated with new commands
6. Weak stubs updated for new API signatures (src/drivers/ata_pio.c)
7. Mount table increased from 8 to 16 slots (previous commit)
Tulio A M Mendes [Fri, 13 Feb 2026 07:39:05 +0000 (04:39 -0300)]
fix: consolidate kconsole banner + safe disk FS probe order
1. Consolidate redundant PANIC + kconsole banners into a single
message shown by kconsole_enter(). Previously both main.c and
kconsole.c printed separate banners.
2. Fix critical VFS mount ordering bug: diskfs, FAT, and ext2 all
share the same ATA primary master disk at LBA 0. Previously
diskfs_create_root() was called first, and its auto-format
(diskfs_super_load) would overwrite LBA 2-3 if DISKFS_MAGIC
was not found -- destroying any ext2 superblock at the same
location. Now the probe order is:
a) ext2 (checks magic 0xEF53 at byte 1080)
b) FAT (validates BPB fields at LBA 0)
c) diskfs (fallback, may auto-format blank disks)
Only one FS is mounted per device -- they are mutually exclusive.
3. Make ata_pio_init_primary_master() idempotent with a static
guard flag so it can be called from both init.c and
diskfs_create_root() without double-initializing.
4. Increase VFS mount table from 8 to 16 slots to prevent
-ENOSPC when adding future mount points.
Bug fixes:
1. dmesg pollution from mem/ls/cat: added klog_set_suppress() flag
in console.c. kconsole wraps command execution with suppression
so interactive output doesn't contaminate the kernel log buffer.
2. UTF-8 em dash rendering as garbage on VGA: replaced all UTF-8
em dashes in output strings (main.c, init.c) with ASCII '--'.
VGA text mode only supports CP437, not UTF-8.
3. PANIC message appearing in dmesg: changed from kprintf() to
console_write() so the emergency banner goes to screen/serial
but NOT to the kernel log ring buffer.
Features:
1. VGA scrollback buffer (200 lines): lines that scroll off the
top of the screen are saved in a circular buffer. Shift+PgUp
scrolls back half-page, Shift+PgDn scrolls forward. Any new
output auto-returns to live view (like Linux).
2. Command history (16 entries): Up/Down arrow keys navigate
through previous commands. Duplicate suppression. Current
line is saved/restored when navigating.
3. Full line editing: Left/Right arrows move cursor, Home/End
jump to start/end, Delete key, insert-at-cursor mode.
Emacs keybindings: Ctrl-A (home), Ctrl-E (end), Ctrl-U
(kill line), Ctrl-K (kill to end).
4. Extended keyboard driver: HAL x86 keyboard now tracks shift
state, handles 0xE0 extended scancodes, emits VT100 escape
sequences for arrow keys/Home/End/PgUp/PgDn/Delete. Shift
scancode map added for uppercase letters and symbols.
Shift+PgUp/PgDn calls vga_scroll_back/fwd directly.
5. Serial input works with readline: VT100 sequences from
terminal emulators are parsed by the same escape state
machine, so -serial stdio now supports full line editing.
1. Double echo: TTY keyboard callback was echoing characters via
tty_output_char() AND kconsole was echoing via console_write().
Fix: keyboard_set_callback(0) detaches TTY on kconsole entry.
2. Backspace printing garbage: VGA vga_put_char() only handled \n,
treating \b as a visible glyph. Fix: full control char support
in vga_put_char — \b moves cursor back, \r resets column, \t
advances to next tab stop, non-printable chars filtered.
3. Enter printing garbage: same root cause as #2 — \r was not
handled by VGA. Now handled properly.
4. Cursor not tracking: VGA had no hardware cursor update. Added
hal_video_set_cursor() HAL function using CRTC registers
0x3D4/0x3D5 on x86, called after every character output.
5. Clear screen broken: was using ANSI escape \033[2J which VGA
can't parse. Added vga_clear() function, kconsole calls it
directly.
6. dmesg contamination: kconsole prompts and help text used
kprintf() which appends to klog ring buffer. Introduced
kc_puts() wrapper over console_write() for interactive output
that should NOT appear in dmesg.
7. Scroll (Shift+PageUp/Down): deferred — requires scrollback
buffer (significant feature). Documented as known limitation.
8. Serial input not working (-serial stdio): kgetc() only read
from PS/2 keyboard via keyboard_read_blocking(). Added
hal_uart_try_getc() to HAL (poll UART LSR data-ready bit),
rewrote kgetc() to poll both keyboard and UART in a loop.
9. ring3 command: removed from kconsole (useless without initrd
in emergency mode). Replaced with proper ls command using
readdir to list directory contents.
HAL changes (all 4 architectures):
- hal_video_set_cursor(row, col) — x86 uses VGA CRTC I/O ports
- hal_uart_try_getc() — non-blocking serial RX polling
Tulio A M Mendes [Fri, 13 Feb 2026 06:18:24 +0000 (03:18 -0300)]
fix: kconsole fallback not activating when initrd is missing
Root cause: init_start() always called arch_platform_start_userspace()
even when fs_root was NULL. The userspace init thread was created
asynchronously and returned 0 (success) to kernel_main(), so the
'init_ret < 0' check never triggered kconsole_enter(). The init
thread would later discover fs_root==NULL, print '[ELF] fs_root
missing' and hang — but kconsole was never entered.
Fix: init_start() now checks fs_root before attempting to start
userspace. If no root filesystem exists (e.g. missing initrd module
in GRUB), it returns -1 immediately, triggering the kconsole
emergency console fallback in kernel_main().
Also added clear panic message before kconsole entry (similar to
Linux's 'Kernel panic - not syncing: VFS: Unable to mount root fs').
Tested: boot without initrd module now shows:
[INIT] No root filesystem — cannot start userspace.
[PANIC] Userspace init failed — dropping to emergency console.
*** AdrOS Kernel Console (kconsole) ***
kconsole>
Tulio A M Mendes [Fri, 13 Feb 2026 06:07:43 +0000 (03:07 -0300)]
fix: deep audit — VA collision, arch pollution, broken stubs
Critical bug fix:
- VDSO and E1000 both mapped at VA 0xC0230000 — silent corruption!
VDSO moved to 0xC0280000, E1000 moved to 0xC0330000-0xC0371FFF
New centralized VA map (include/kernel_va_map.h):
- All fixed kernel VA allocations documented in one header
- IOAPIC, VDSO, ACPI, ATA DMA, E1000, LAPIC all use KVA_* defines
- Prevents future VA collisions — single source of truth
Architecture pollution fixes:
- syscall.c SET_THREAD_AREA: replaced x86 gdt_set_gate_ext() with
hal_cpu_set_tls() HAL call (was #if __i386__ inline)
- e1000_netif.c: replaced x86 'pause' asm with cpu_relax() from
spinlock.h (arch-agnostic)
- tty.c: removed dead uart_console.h include (switched to
console_put_char in previous commit)
Broken code fix:
- pmm_print_stats(): was declared in pmm.h but never implemented —
now prints Total/Used/Free RAM in KB and MB
- kconsole 'mem' command: replaced [TODO] stub with pmm_print_stats()
Kernel:
- Open /dev/console as fd 0/1/2 for init process (mirrors Linux
kernel_init: open + dup + dup pattern)
- Add console_put_char() that outputs to both UART and VGA
- TTY write path now routes through console_put_char() instead of
uart_put_char() only — userspace output now appears on VGA too
- Implement OPOST/ONLCR output processing: \n → \r\n conversion
(POSIX termios c_oflag, enabled by default)
- TCGETS/TCSETS ioctl now reads/writes c_oflag
- All TTY echo paths (canonical, raw, line editing) use tty_output_char()
for consistent UART+VGA output with OPOST processing
- Increase syscall write copy buffer from 256 to 1024 bytes
- Declare vga_put_char() in vga_console.h
Userspace (ulibc):
- stdout is now line-buffered (_STDIO_LBUF): flushes on \n
- stderr is now unbuffered (_STDIO_UNBUF): writes immediately
- printf()/vprintf() now go through fwrite(stdout) instead of raw
write(), unifying all stdio output through the FILE buffer
- putchar()/puts() also route through fwrite(stdout)
- fwrite() respects buffering modes: unbuffered bypasses buffer,
line-buffered flushes on newline, full-buffered flushes when full
- Add setvbuf()/setbuf() with _IOFBF/_IOLBF/_IONBF modes
- Add isatty() implemented via TCGETS ioctl probe (POSIX standard)
Tulio A M Mendes [Fri, 13 Feb 2026 05:07:22 +0000 (02:07 -0300)]
feat: mount FAT and ext2 filesystems from init.c
- Probe IDE disk at LBA 0 for FAT and ext2 signatures during boot
- FAT mounts at /fat, ext2 mounts at /ext2
- Both fail gracefully on unformatted/zeroed disks (no panic)
- diskfs remains at /disk as primary RW filesystem
Tulio A M Mendes [Fri, 13 Feb 2026 04:31:04 +0000 (01:31 -0300)]
kprintf: migrate all uart_print() calls to kprintf() (Route A)
Replace 270 direct uart_print() calls across 42 files with kprintf(),
routing all kernel messages through the klog ring buffer and multi-backend
console infrastructure (UART + VGA).
Key changes:
- All kernel log/debug messages now go through kprintf() -> klog_append()
-> console_write(), ensuring they appear in dmesg and on all enabled
output devices.
- Consolidated multi-call patterns (uart_print+itoa_hex) into single
kprintf() calls with format specifiers (%x, %u, %s, %d, %c).
- Removed manual itoa/itoa_hex + uart_print concatenation throughout.
- Cleaned up stale #include uart_console.h from files that no longer
need it (main.c, socket.c, syscall.c, slab.c, timer.c).
- uart_print() now only remains in 2 places:
* uart_console.c (the implementation)
* console.c (the UART backend in console_write)
- uart_put_char() retained in tty.c for direct terminal I/O (not logging).
- arch_early_setup files keep uart_console.h for uart_init() call.
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.