commit - 59b10ca5186d691ee2071af0a3aef145ee195dc2
commit + a91825ce61ca2e31c3990fcc0084552ebd8f23f4
blob - c3fba39129b4073242a9e30c75f445a83e2b40ab
blob + 3886cf9661c1f94ff039003cf6984e58efe3a7a9
--- kernel/arch/x86_64/isr.c
+++ kernel/arch/x86_64/isr.c
__attribute__((interrupt)) static void
x86_fault_gpf(struct interrupt_frame *frame, uint64_t error_code)
{
+ bool user_mode = (frame->cs & 0x3) == 0x3;
+
printk("[trap] general protection fault\n");
trap_print_hex64(" error_code=", error_code);
x86_log_gpf(error_code);
}
}
trap_print_hex64(" last_iret_cs=", x86_last_iret_cs);
+ printk(" task=");
+ printk(sched_current_task_name());
+ printk("\n");
+ if (user_mode)
+ x86_dump_user_stack(frame->rsp);
x86_exception_log_frame(frame);
+ if (user_mode) {
+ struct sched_task *task = sched_current_task();
+
+ printk("[trap] user GPF -> SIGSEGV and task exit\n");
+ if (task != NULL) {
+ sched_task_set_exit_status(task,
+ wait_status_signal(SIGSEGV, false));
+ sched_exit_current();
+ }
+ }
panic("general protection fault");
}
blob - cd77c6907b6af191481a6ed4da470f294d11a392
blob + 1ca53ff2e1283aaa0ad036ef2170da5afda5b6a5
--- kernel/arch/x86_64/serial.c
+++ kernel/arch/x86_64/serial.c
serial_outb(COM1 + 1, 0x01); /* Enable received-data interrupts. */
x86_idt_set_gate(COM1_VECTOR, x86_serial_isr);
pic_unmask_irq(COM1_IRQ);
+#ifndef UEFI_BUILD
+ printk("[serial_init] IRQs ENABLED: COM1_IRQ=4 COM1_VECTOR=36\n");
#endif
+#else
+#ifndef UEFI_BUILD
+ printk("[serial_init] POLLING mode (no IRQs)\n");
+#endif
+#endif
}
/*
/*
* serial_putc - write a single character, expanding '\n' to CRLF.
+ * Non-blocking: if TX register is not ready, character is dropped to prevent
+ * deadlock when echo blocks input processing.
*/
void
serial_putc(char c)
{
if (c == '\n')
serial_putc('\r'); /* Preserve CRLF expectations for consoles. */
- while (!serial_ready())
- /* spin until hardware is ready */ ;
+ /* Non-blocking: check TX ready once, drop if not available.
+ * This prevents echo from blocking input processing when serial TX is busy.
+ * For critical output, use serial_write_blocking() instead. */
+ if (!serial_ready())
+ return; /* TX busy - drop character to avoid blocking */
serial_outb(COM1, (unsigned char)c);
}
void
serial_irq(void)
{
+ static uint32_t irq_count = 0;
+ /* Log FIRST call unconditionally to confirm IRQ handler is invoked */
+ if (irq_count == 0) {
+ printk("[serial_irq] FIRST CALL - IRQ handler active\n");
+ }
+ if ((++irq_count % 10) == 0) {
+ printk("[serial_irq] called #");
+ char buf[16];
+ uint32_t val = irq_count / 10;
+ int idx = 0;
+ if (val == 0) {
+ buf[idx++] = '0';
+ } else {
+ char tmp[16];
+ int t = 0;
+ while (val > 0) {
+ tmp[t++] = '0' + (val % 10);
+ val /= 10;
+ }
+ while (t > 0)
+ buf[idx++] = tmp[--t];
+ }
+ buf[idx] = '\0';
+ printk(buf);
+ printk("*10\n");
+ }
while ((serial_inb(COM1 + 2) & 0x01) == 0) {
unsigned char lsr = serial_inb(COM1 + 5);
if ((lsr & 0x01) == 0)
break;
- serial_buffer_push((char)serial_inb(COM1 + 0));
+ char ch = (char)serial_inb(COM1 + 0);
+ /* Log every character received via IRQ */
+ {
+ const char hex[] = "0123456789abcdef";
+ printk("[serial_irq_rx] ch=0x");
+ char buf[3];
+ unsigned char uc = (unsigned char)ch;
+ buf[0] = hex[(uc >> 4) & 0xf];
+ buf[1] = hex[uc & 0xf];
+ buf[2] = '\0';
+ printk(buf);
+ if (uc >= 0x20 && uc <= 0x7e) {
+ printk(" '");
+ printk(&ch);
+ printk("'");
+ }
+ printk("\n");
+ }
+ serial_buffer_push(ch);
}
}
static int serial_poll_debug;
#endif
unsigned char lsr;
+ static uint32_t poll_call_count = 0;
+ /* Log FIRST poll call to confirm function is being invoked */
+ if (poll_call_count == 0) {
+ printk("[serial_poll_rx] FIRST CALL (IRQ mode, SERIAL_WITH_IRQ=1)\n");
+ /* Log serial port state */
+ lsr = serial_inb(COM1 + 5);
+ printk("[serial_poll_rx] LSR=0x");
+ {
+ const char hex[] = "0123456789abcdef";
+ char buf[3];
+ buf[0] = hex[(lsr >> 4) & 0xf];
+ buf[1] = hex[lsr & 0xf];
+ buf[2] = '\0';
+ printk(buf);
+ }
+ printk(" (bit0=DR: ");
+ printk((lsr & 0x01) ? "1" : "0");
+ printk(")\n");
+ }
+ poll_call_count++;
+
+ /* Log periodic polling to confirm serial_poll_rx is being called after shell prompt */
+ #ifdef LENIX_DEBUG
+ if (poll_call_count % 100000 == 0 && poll_call_count > 0) {
+ lsr = serial_inb(COM1 + 5);
+ printk("[serial_poll_rx] poll #");
+ char buf[16];
+ uint32_t val = poll_call_count / 100000; /* Reduced frequency: every 100k instead of 10k */
+ int idx = 0;
+ if (val == 0) {
+ buf[idx++] = '0';
+ } else {
+ char tmp[16];
+ int t = 0;
+ while (val > 0 && t < 15) {
+ tmp[t++] = '0' + (val % 10);
+ val /= 10;
+ }
+ while (t > 0)
+ buf[idx++] = tmp[--t];
+ }
+ buf[idx] = '\0';
+ printk(buf);
+ printk("*100000 LSR=0x");
+ const char hex[] = "0123456789abcdef";
+ char lsr_buf[3];
+ lsr_buf[0] = hex[(lsr >> 4) & 0xf];
+ lsr_buf[1] = hex[lsr & 0xf];
+ lsr_buf[2] = '\0';
+ printk(lsr_buf);
+ printk(" (DR=");
+ printk((lsr & 0x01) ? "1" : "0");
+ printk(")\n");
+ }
+ #endif
+
+ /* Poll hardware FIFO for new characters */
for (;;) {
lsr = serial_inb(COM1 + 5);
if ((lsr & 0x01) == 0)
#if SERIAL_DEBUG_RX
static int serial_poll_debug;
#endif
+ static uint32_t poll_call_count = 0;
+ unsigned char lsr;
+ /* Log FIRST poll call to confirm function is being invoked */
+ if (poll_call_count == 0) {
+ printk("[serial_poll_rx] FIRST CALL (non-IRQ mode, SERIAL_WITH_IRQ=0)\n");
+ lsr = serial_inb(COM1 + 5);
+ printk("[serial_poll_rx] LSR=0x");
+ {
+ const char hex[] = "0123456789abcdef";
+ char buf[3];
+ buf[0] = hex[(lsr >> 4) & 0xf];
+ buf[1] = hex[lsr & 0xf];
+ buf[2] = '\0';
+ printk(buf);
+ }
+ printk(" (bit0=DR: ");
+ printk((lsr & 0x01) ? "1" : "0");
+ printk(")\n");
+ }
+ poll_call_count++;
+
while ((serial_inb(COM1 + 5) & 0x01) != 0) {
char ch = (char)serial_inb(COM1 + 0);
#if SERIAL_DEBUG_RX
blob - a2f7ebb42e585147dc19e05ecbd2fcc624a59bc1
blob + 38e7bbfc78a8bb640290e50fa70c5bd321043256
--- kernel/arch/x86_64/signal.c
+++ kernel/arch/x86_64/signal.c
#include <stdint.h>
#include "arch/x86_64/signal.h"
+#include "log/printk.h"
#include "mm/aspace.h"
#include "sched/task.h"
#include "sys/signal.h"
return 0;
if (signal_state_get_action(state, sig, &act) != 0)
return 0;
+
+ /* Safety check: validate handler address IMMEDIATELY after reading it.
+ * This prevents null pointer dereferences if the handler is invalid.
+ * Must check before any other handler comparisons. */
+ if (act.handler != (uint64_t)SIG_IGN &&
+ act.handler != (uint64_t)SIG_DFL &&
+ (act.handler == 0 || act.handler >= 0x800000000000ULL)) {
+ /* Invalid handler address - treat as SIG_DFL TERM */
+ #ifdef LENIX_DEBUG
+ printk("[signal] invalid handler address 0x");
+ {
+ const char hex[] = "0123456789abcdef";
+ char buf[17];
+ uint64_t val = act.handler;
+ int idx = 0;
+ for (int shift = 60; shift >= 0; shift -= 4) {
+ buf[idx++] = hex[(val >> shift) & 0xf];
+ }
+ buf[idx] = '\0';
+ printk(buf);
+ printk(" for sig=");
+ printk_dec((uint64_t)sig);
+ printk(" task=");
+ printk(sched_task_name(task));
+ printk("\n");
+ }
+ #endif
+ signal_state_clear(state, sig);
+ sched_task_set_exit_status(task,
+ wait_status_signal(sig, false));
+ sched_exit_current();
+ /* NOTREACHED */
+ }
+
if (act.handler == (uint64_t)SIG_IGN) {
signal_state_clear(state, sig);
return 0;
signal_state_update_blocked(state, SIG_SETMASK, new_mask, NULL);
}
+ /* Handler was already validated above, so it's safe to use here.
+ * Double-check as a safety measure (should never trigger if validation above works). */
+ if (act.handler == 0 || act.handler >= 0x800000000000ULL) {
+ /* This should never happen - handler was validated above.
+ * But if it does, treat as fatal error. */
+ #ifdef LENIX_DEBUG
+ printk("[signal] CRITICAL: handler became invalid after validation! handler=0x");
+ {
+ const char hex[] = "0123456789abcdef";
+ char buf[17];
+ uint64_t val = act.handler;
+ int idx = 0;
+ for (int shift = 60; shift >= 0; shift -= 4) {
+ buf[idx++] = hex[(val >> shift) & 0xf];
+ }
+ buf[idx] = '\0';
+ printk(buf);
+ printk(" sig=");
+ printk_dec((uint64_t)sig);
+ printk(" task=");
+ printk(sched_task_name(task));
+ printk("\n");
+ }
+ #endif
+ sched_task_set_exit_status(task,
+ wait_status_signal(sig, false));
+ sched_exit_current();
+ /* NOTREACHED */
+ }
+
regs->rcx = act.handler;
regs->rdi = (uint64_t)sig;
regs->rsi = frame_addr;
blob - a0f279fe63ba6ee14b39b6749c247a0a530f4075
blob + a35f03672feb7454dcb0e80d2fd8c6d76cc67b41
--- kernel/console/line.c
+++ kernel/console/line.c
line_route_char(char c, uint32_t flags)
{
static int tty_queue_warned;
+ uint32_t active_pty;
- if (console_service_fallback_enabled() &&
- console_service_tty_task() == NULL) {
+ active_pty = console_pty_active();
+
+ /* PTY 0 (console) doesn't route individual characters - completed lines
+ * are handled via console_line_listener() -> console_queue_push().
+ * Individual character routing is only for non-zero PTYs. */
+ if (active_pty == 0) {
+ return;
+ }
+
+ /* If tty_task is not registered, use direct forwarding as fallback */
+ if (console_service_tty_task() == NULL) {
console_pty_forward_line(&c, 1);
return;
}
- if (console_service_tty_queue_char(c, flags))
+ /* Try to queue character for ttyd - this handles canonical mode editing */
+ if (console_service_tty_queue_char(c, flags)) {
+ tty_queue_warned = 0; /* Reset warning counter on success */
return;
- if (tty_queue_warned < 4) {
- printk("[console] tty queue saturated; dropping input\n");
- tty_queue_warned++;
}
+ /* If queueing failed, we have two options:
+ * 1. Drop the character (current behavior) - but this loses input if ttyd is deadlocked
+ * 2. Fall back to direct forwarding - bypasses canonical mode but preserves input
+ *
+ * We'll use a hybrid: if queue fails repeatedly, fall back to direct forwarding
+ * to prevent input loss if ttyd is blocked or deadlocked. */
+ static uint32_t queue_fail_count = 0;
+ queue_fail_count++;
+ if (queue_fail_count < 10) {
+ /* First few failures: warn and drop (ttyd might catch up) */
+ if (tty_queue_warned < 4) {
+ printk("[console] tty queue full, dropping character (ttyd will catch up)\n");
+ tty_queue_warned++;
+ }
+ } else {
+ /* Many failures: ttyd is likely blocked, fall back to direct forwarding */
+ if (tty_queue_warned < 5) {
+ printk("[console] tty queue persistently full, falling back to direct forwarding\n");
+ tty_queue_warned++;
+ }
+ console_pty_forward_line(&c, 1);
+ queue_fail_count = 0; /* Reset counter after fallback */
+ }
}
static void
static void
emit_line(void)
{
+ uint32_t active_pty = console_pty_active();
+
if (line_len >= LINE_BUF_LEN)
line_len = LINE_BUF_LEN - 1;
line_buf[line_len] = '\0';
+
if (hook_cb != NULL)
hook_cb(line_buf, hook_arg);
/* Notify the passive listener (console server) as well. */
uint32_t active_pty;
bool raw_cr;
+
/* Ctrl+N always drives PTY cycling regardless of mode */
if ((unsigned char)c == 0x0e) {
console_service_control_event(CONSOLE_PTY_CTRL_NEXT);
blob - fdcd63da88ce519ec17064ced9a1d19699452d39
blob + 2ab51d58027262200645f45f122644896bc4c653
--- kernel/console/pty.c
+++ kernel/console/pty.c
uint32_t new_id;
int wait_iters = 0;
- while (console_service_task == NULL && wait_iters < 100000) {
- sched_yield();
+ printk("[pty] console_pty_alloc called: owner=");
+ printk(owner ? sched_task_name(owner) : "NULL");
+ printk(" console_service_task=");
+ printk(console_service_task ? sched_task_name(console_service_task) : "NULL");
+ printk("\n");
+
+ while (console_service_task == NULL && wait_iters < 1000) {
+ /* Sleep for 10ms at 100Hz instead of busy-waiting */
+ sched_sleep_ticks(1);
wait_iters++;
}
- if (owner == NULL || console_service_task == NULL)
+ if (wait_iters > 0) {
+ printk("[pty] waited ");
+ printk_dec((uint64_t)wait_iters);
+ printk(" iterations\n");
+ }
+ if (owner == NULL || console_service_task == NULL) {
+ printk("[pty] alloc failed: owner or console_service_task NULL, returning -EINVAL=");
+ printk_dec((uint64_t)(-SYSCALL_EINVAL));
+ printk("\n");
return -SYSCALL_EINVAL;
+ }
mbox = sched_task_mailbox(owner);
if (mbox == NULL)
return -SYSCALL_EINVAL;
handle = ipc_portal_create(owner, console_service_task,
IPC_PORTAL_RIGHT_SEND);
+ printk("[pty] portal_create returned handle=");
+ printk_dec((uint64_t)handle);
+ printk("\n");
if (handle == IPC_PORTAL_INVALID_HANDLE) {
printk("[pty] alloc failed: portal_create returned INVALID\n");
spinlock_lock(&console_pty_lock);
*pty_id_out = pty->id;
if (handle_out != NULL)
*handle_out = handle;
+ printk("[pty] console_pty_alloc success: pty_id=");
+ printk_dec((uint64_t)pty->id);
+ printk(" handle=");
+ printk_dec((uint64_t)handle);
+ printk(" returning 0\n");
return 0;
}
struct console_pty_entry *pty;
bool is_supervisor = false;
bool can_activate = false;
+ const char *req_name = requester ? sched_task_name(requester) : "NULL";
if (pty_id != 0) {
spinlock_lock(&console_pty_lock);
can_activate = (pty != NULL && pty->slave.task == requester);
/* Allow console service to activate any PTY */
bool is_console_service = (requester == console_service_task);
+ /* Allow parent to activate PTY owned by its direct child (for spawn scenarios) */
+ bool is_parent = false;
+ if (pty != NULL && pty->slave.task != NULL && requester != NULL) {
+ uint64_t requester_pid = sched_task_id(requester);
+ uint64_t child_parent_pid = sched_task_parent_pid(pty->slave.task);
+ /* Check if requester is the parent of the PTY owner */
+ if (child_parent_pid == requester_pid)
+ is_parent = true;
+ }
if (pty == NULL) {
spinlock_unlock(&console_pty_lock);
+ printk("[pty] set_active fail: missing id=");
+ printk_dec((uint64_t)pty_id);
+ printk(" requester=");
+ printk(req_name);
+ printk("\n");
return -SYSCALL_EINVAL;
}
- if (!can_activate && !is_supervisor && !is_console_service) {
+ if (!can_activate && !is_supervisor && !is_console_service && !is_parent) {
spinlock_unlock(&console_pty_lock);
+ printk("[pty] set_active deny id=");
+ printk_dec((uint64_t)pty_id);
+ printk(" owner=");
+ printk(pty->slave.task ? sched_task_name(pty->slave.task) : "NULL");
+ printk(" requester=");
+ printk(req_name);
+ printk(" is_supervisor=");
+ printk(is_supervisor ? "1" : "0");
+ printk(" is_console=");
+ printk(is_console_service ? "1" : "0");
+ printk("\n");
return -SYSCALL_EPERM;
}
/* Save previous PTY when switching away from it */
/* Send SIGWINCH to newly active PTY to trigger shell prompt redisplay */
if (console_pty_active_id != 0)
console_pty_send_signal(console_pty_active_id, 28); /* SIGWINCH */
+ printk("[pty] set_active SUCCESS id=");
+ printk_dec((uint64_t)pty_id);
+ printk(" active_pty_id=");
+ printk_dec((uint64_t)console_pty_active_id);
+ printk("\n");
return 0;
}
console_pty_send_signal(uint32_t pty_id, uint32_t signal)
{
struct console_pty_endpoint ep = {0};
- #ifdef LENIX_DEBUG
- char buf[32];
- int written;
- #endif
bool found = false;
uint64_t fg_pgrp = 0;
bool delivered = false;
if (!delivered)
return -SYSCALL_ESRCH;
- /* Only inject the textual hint in debug builds; it confuses shells. */
- #ifdef LENIX_DEBUG
- written = lenix_snprintf(buf, sizeof(buf), "[signal %u]\n", signal);
- if (written > 0)
- console_pty_send_to_slave(&ep, buf, (size_t)written);
- #endif
+ /* NOTE: Signal debug messages were removed because they were being sent
+ * to the PTY slave endpoint (shell stdin), causing the shell to receive
+ * "[signal %u]" as input and try to execute it as a command. If signal
+ * debugging is needed, it should be sent to the console service or logged
+ * via printk() instead of the PTY slave. */
return 0;
}
return 0;
}
+struct ipc_mailbox *
+console_pty_get_slave_mailbox(uint32_t pty_id)
+{
+ struct ipc_mailbox *mbox = NULL;
+
+ if (pty_id == 0)
+ return NULL;
+ spinlock_lock(&console_pty_lock);
+ struct console_pty_entry *pty = console_pty_find_by_id_locked(pty_id);
+ if (pty != NULL && pty->slave.mailbox != NULL) {
+ mbox = pty->slave.mailbox;
+ }
+ spinlock_unlock(&console_pty_lock);
+ return mbox;
+}
+
static void
console_pty_apply_active_flags(void)
{
blob - 31b58b25573823a43e48779adca80212fbc1145f
blob + 5acc73906b7b64559273d029b872ebd0c26cff02
--- kernel/console/service.c
+++ kernel/console/service.c
#define CONSOLE_BLOCKTRACE_MAX 32U
-static void
+void
+console_note_work_available(void)
+{
+ /* Stub function for now - work availability tracking not yet implemented */
+}
+
+void
console_wake_service_task(void)
{
- if (console_task != NULL)
+ static uint32_t wake_debug_count = 0;
+ if (console_task != NULL && console_task != sched_current_task()) {
+ if (wake_debug_count < 10) {
+ // printk("[console_wake] waking console_task\n");
+ wake_debug_count++;
+ }
sched_wake(console_task);
+ } else if (wake_debug_count < 10) {
+ // printk("[console_wake] SKIP: task=");
+ // printk(console_task ? "valid" : "NULL");
+ // printk(" current=");
+ // printk(console_task == sched_current_task() ? "SAME" : "DIFF");
+ // printk("\n");
+ wake_debug_count++;
+ }
}
static bool
console_ttyctl_task = NULL;
console_line_register_listener(console_line_listener, NULL);
console_line_set_echo(true);
+ printk("[console_service_init] spawning console task\n");
console_task = sched_spawn_kernel("console", console_task_main, NULL);
console_service_task = console_task;
if (console_task == NULL) {
+ printk("[console_service_init] FAILED to spawn console task\n");
boot_log_status("console: interactive service online",
BOOT_LOG_STATUS_FAIL);
} else {
+ printk("[console_service_init] console task spawned successfully\n");
console_pty_attach_service(console_task);
console_pty_set_master(console_master_write, NULL);
boot_log_status("console: interactive service online",
}
static void
-console_task_main(void *arg)
+ console_task_main(void *arg)
{
struct console_queue_entry entry;
enum console_pty_control_event ctrl_ev;
static int console_idle_debug;
+ static uint32_t idle_iterations = 0;
(void)arg;
+ printk("[console_task] STARTING - before prompt\n");
console_print_prompt();
- static int console_loop_count = 0;
+ printk("[console_task] started, entering main loop\n");
+ static uint64_t console_loop_count __attribute__((unused)) = 0;
for (;;) {
#ifdef LENIX_DEBUG
- if (console_loop_count == 0 || console_loop_count == 1) {
+ /* Log first few iterations and then every 1000th iteration to confirm task is running */
+ if (console_loop_count < 5 || (console_loop_count % 1000 == 0)) {
printk("[console] loop iteration ");
console_print_uint64(console_loop_count);
printk("\n");
}
#endif
console_loop_count++;
- if (console_loop_count > 10)
- console_loop_count = 10;
+ /* Don't cap the counter - let it wrap naturally if needed */
while (console_ctrl_pop(&ctrl_ev)) {
if (diag_flag_enabled(DIAG_FLAG_POLICY)) {
printk("[diag] console ctrl dispatch ev=");
}
console_pty_control_event(ctrl_ev);
}
+ /* Log periodic heartbeat to confirm console task is running (even in release builds).
+ * Log every 100000 iterations to avoid flooding the log. */
+ #ifdef LENIX_DEBUG
+ if (console_loop_count % 100000 == 0 && console_loop_count > 0) {
+ printk("[console] heartbeat: loop=");
+ console_print_uint64(console_loop_count);
+ printk("\n");
+ }
+ #endif
console_tty_drain_queue();
+ /* Always poll serial for input, even if queue has entries */
+ /* Log periodic polling to confirm console task is running after shell prompt */
+ #ifdef LENIX_DEBUG
+ if (console_loop_count % 100000 == 0 && console_loop_count > 0) {
+ printk("[console] polling serial (loop=");
+ console_print_uint64(console_loop_count);
+ printk(")\n");
+ }
+ #endif
+ serial_poll_rx();
if (!queue_pop(&entry)) {
- /* Drain UART then yield; avoids hangs on missed wakeups */
- serial_poll_rx();
- if (console_idle_debug < 4) {
+ /* Queue empty - but we MUST keep polling serial for input.
+ * This is critical: before the yield storm fix, serial_poll_rx()
+ * ran from timer interrupts every tick, so input was always processed.
+ * Now it only runs when the console task is scheduled, so we must
+ * stay scheduled as much as possible.
+ *
+ * Yield periodically when idle to allow other tasks to run.
+ * We yield every 10 idle iterations to prevent CPU monopolization
+ * while still processing input promptly when available. */
+ idle_iterations++;
+ if (console_idle_debug < 10) {
#ifdef LENIX_DEBUG
- printk("[console] idle, polling serial\n");
+ printk("[console] idle, polling serial (iter=");
+ console_print_uint64(idle_iterations);
+ printk(")\n");
#endif
console_idle_debug++;
}
- sched_yield();
+ /* Yield periodically when idle (every 100000 idle iterations) to allow
+ * other tasks to run. We yield very infrequently to ensure we stay
+ * scheduled and can poll serial input frequently. The scheduler will
+ * preempt us on timer ticks if other tasks need CPU time.
+ *
+ * CRITICAL: Console task must stay runnable to process keyboard input.
+ * If we yield too frequently and all other tasks are blocked on I/O,
+ * the system can deadlock. Use a very high threshold (100k iterations)
+ * to minimize yield frequency while still allowing other tasks to run. */
+ if (idle_iterations >= 100000) {
+ idle_iterations = 0;
+ sched_yield();
+ }
+ /* Continue immediately to keep polling serial */
continue;
}
+ /* Reset idle counter when we have work */
+ idle_iterations = 0;
if (entry.len > CONSOLE_LINE_MAX) {
entry.len = CONSOLE_LINE_MAX;
entry.buf[CONSOLE_LINE_MAX - 1] = '\0';
{
struct console_tty_event ev;
+
if (console_tty_portal == IPC_PORTAL_INVALID_HANDLE ||
- console_tty_task == NULL)
+ console_tty_task == NULL) {
return false;
+ }
memset(&ev, 0, sizeof(ev));
ev.opcode = CONSOLE_TTY_EVENT_CHAR;
ev.pty_id = console_pty_active();
- if (ev.pty_id == 0)
+ if (ev.pty_id == 0) {
return false;
+ }
ev.flags = flags;
ev.length = 1;
ev.data[0] = c;
- if (ipc_portal_send(console_tty_portal, &ev, sizeof(ev)) != 0)
+
+ /* Check mailbox depth before sending to avoid blocking if mailbox is full.
+ * If the mailbox is getting too full (>50 messages) or the lock is held,
+ * skip sending to prevent the console task from blocking in mailbox_lock()
+ * or kmem_alloc(). This prevents system-wide deadlocks. */
+ struct ipc_mailbox *mbox = ipc_portal_target_mailbox(console_tty_portal);
+ if (mbox != NULL) {
+ /* Try to acquire lock with a quick check - if we can't get it immediately,
+ * the mailbox is likely contended and we should skip this send. */
+ uint32_t lock_val = __atomic_exchange_n(&mbox->lock, 1U, __ATOMIC_ACQUIRE);
+ if (lock_val != 0) {
+ /* Lock was held - release our failed acquisition and skip send */
+ __atomic_store_n(&mbox->lock, 0U, __ATOMIC_RELEASE);
+ return false;
+ }
+ /* Lock acquired - check depth */
+ size_t depth = mbox->depth;
+ if (depth > 50) {
+ /* Mailbox too full - release lock and skip send */
+ __atomic_store_n(&mbox->lock, 0U, __ATOMIC_RELEASE);
+ return false;
+ }
+ /* Release lock - ipc_portal_send will acquire it again */
+ __atomic_store_n(&mbox->lock, 0U, __ATOMIC_RELEASE);
+ }
+
+ /* Add debug logging before portal_send to catch hangs */
+
+ /* Try to send - if mailbox is full or ttyd is blocked, drop the character
+ * rather than hanging the console task. This prevents system-wide deadlocks. */
+ int send_rc = ipc_portal_send(console_tty_portal, &ev, sizeof(ev));
+ if (send_rc != 0) {
+ /* If send fails, check if it's because mailbox is full.
+ * In that case, we should drop the character rather than retrying,
+ * as retrying could cause a deadlock if ttyd is blocked. */
return false;
+ }
return true;
}
size_t next;
if (console_tty_portal == IPC_PORTAL_INVALID_HANDLE ||
- console_tty_task == NULL)
+ console_tty_task == NULL) {
return false;
+ }
spinlock_lock(&char_queue_lock);
* is not draining the queue fast enough. Log this to alert
* administrators that the TTY daemon may be hung or unresponsive.
*/
- printk("[console] WARN: char queue full, drops=");
- console_print_uint64(console_char_queue_drops);
- printk("\n");
return false;
}
console_char_tail++;
spinlock_unlock(&char_queue_lock);
+
+
console_wake_service_task();
return true;
}
{
struct console_char_event ev;
- while (console_tty_char_pop(&ev))
- console_tty_send_char(ev.c, ev.flags);
+ while (console_tty_char_pop(&ev)) {
+ /* Skip null characters - these indicate uninitialized queue entries */
+ if (ev.c == 0) {
+ /* Don't send null characters - they're invalid */
+ continue;
+ }
+ if (!console_tty_send_char(ev.c, ev.flags)) {
+ /* If send fails (mailbox full or ttyd blocked), drop the character
+ * and continue draining the queue. This prevents the console task
+ * from hanging if ttyd is blocked or not receiving messages. */
+ continue;
+ }
+ }
}
int
{
uint32_t allowed = CONSOLE_PTY_TERMIOS_FLAGS_LOW_MASK;
- if ((flags & ~allowed) != 0)
+ if ((flags & ~allowed) != 0) {
return false;
- return console_tty_char_push(c, flags);
+ }
+ bool rc = console_tty_char_push(c, flags);
+ return rc;
}
void
blob - 210868dc8e461e618075747055536096a55c100a
blob + 1bde332a878e54903d436b45e2eb1a247df1321d
--- kernel/include/console/pty.h
+++ kernel/include/console/pty.h
uint32_t console_pty_active(void);
uint32_t console_pty_previous(void);
int console_pty_input(uint32_t pty_id, const char *buf, size_t len);
+struct ipc_mailbox *console_pty_get_slave_mailbox(uint32_t pty_id);
blob - c7defa74defd2b1f0de3ba100a3e6ce4ee036987
blob + 0ca440bf0faeff41170804a265c24938a888ffa2
--- kernel/include/console/service.h
+++ kernel/include/console/service.h
size_t console_service_char_queue_drops(void);
size_t console_service_ctrl_queue_drops(void);
size_t console_service_input_drops(void);
+
+/* Wake console service task (e.g., from serial IRQ when input arrives) */
+void console_note_work_available(void);
+void console_wake_service_task(void);
blob - 5b5787fbfee9fc486057d289fe978d716e2ddad4
blob + 609d8e00af92be34cc2ceb47d7ac1d1a0b66c75f
--- kernel/include/ipc/block_service.h
+++ kernel/include/ipc/block_service.h
uint32_t blocks;
uint64_t lba;
uint32_t data_len;
+ uint32_t reserved; /* padding to distinguish from backend response (4124 bytes) */
uint8_t data[IPC_BLOCK_SERVICE_MAX_DATA];
};
blob - a3d8253f070118b5925b01f52437386d02d17f50
blob + b8e776e4fffa5566067edd711ed6b658a784e396
--- kernel/include/ipc/mailbox.h
+++ kernel/include/ipc/mailbox.h
struct ipc_mailbox;
struct sched_task;
struct ipc_portal_transport_ops;
+struct ipc_mailbox_poll_waiter;
#define IPC_MAILBOX_MAX_PAYLOAD 8192U
#define IPC_MAILBOX_CONTENTION_THRESHOLD 100U /* warn if lock waits >100 ticks */
/* wait queue for blocked receivers */
struct sched_task *wait_head;
struct sched_task *wait_tail;
+ struct ipc_mailbox_poll_waiter *poll_wait_head;
+ struct ipc_mailbox_poll_waiter *poll_wait_tail;
};
+struct ipc_mailbox_poll_waiter {
+ struct sched_task *task;
+ struct ipc_mailbox_poll_waiter *next;
+ bool in_list;
+};
+
void ipc_mailbox_init(struct ipc_mailbox *, struct sched_task *);
int ipc_mailbox_send(struct ipc_mailbox *, const void *buf, size_t len);
int ipc_mailbox_send_from(struct ipc_mailbox *, const void *buf, size_t len,
struct sched_task *sender, uint32_t portal_id);
struct ipc_message *ipc_mailbox_recv(struct ipc_mailbox *);
struct ipc_message *ipc_mailbox_recv_block(struct ipc_mailbox *);
+bool ipc_mailbox_has_message(const struct ipc_mailbox *);
+void ipc_mailbox_poll_waiter_add(struct ipc_mailbox *,
+ struct ipc_mailbox_poll_waiter *);
+bool ipc_mailbox_poll_waiter_remove_task(struct ipc_mailbox *,
+ const struct sched_task *);
void ipc_mailbox_free(struct ipc_message *);
void ipc_mailbox_drain(struct ipc_mailbox *);
void ipc_mailbox_set_remote(struct ipc_mailbox *, uint32_t node_id, bool remote);
blob - 01ca8e9c2d60f68326013e27cecc121f10a7a0c3
blob + a4116a4c06bcfc30183188ccfea702d7160141cc
--- kernel/include/ipc/portal.h
+++ kernel/include/ipc/portal.h
uint64_t ipc_portal_message_count(ipc_portal_handle_t);
uint64_t ipc_portal_integrity_violations(ipc_portal_handle_t);
uint64_t ipc_portal_created_ticks(ipc_portal_handle_t);
+struct ipc_mailbox *ipc_portal_target_mailbox(ipc_portal_handle_t);
blob - 0b035678f549d10ed635723092a1615a93bbb6bd
blob + f2f4e5ebb630270b8c787b212f61592954ccd283
--- kernel/include/sched/task.h
+++ kernel/include/sched/task.h
*/
uint64_t sched_task_id(const struct sched_task *);
/*
+ * sched_task_parent_pid - return the parent PID of a specific task
+ */
+uint64_t sched_task_parent_pid(const struct sched_task *);
+/*
* sched_task_pgrp - return the process group ID of a task
*/
uint64_t sched_task_pgrp(const struct sched_task *);
*/
void sched_block_current(void);
void sched_wake(struct sched_task *);
+void sched_sleep_ticks(uint64_t ticks);
+bool sched_sleep_cancel(struct sched_task *);
/* Wait queue linkage for IPC blocking */
struct sched_task *sched_task_wait_next(const struct sched_task *);
blob - 0a4afde5d0435523b7baf038d00578fd27b6d917
blob + 7c585bf0b01b148ccc1e1c2bcd6e82a5ef0782c5
--- kernel/ipc/block_service.c
+++ kernel/ipc/block_service.c
bool in_use;
bool ready;
uint32_t token;
+ volatile uint8_t prepared;
struct sched_task *client;
- struct ipc_block_service_response *resp; /* Allocated from ringbuf data pool */
- uint32_t resp_offset; /* Offset in ringbuf data pool */
+ struct sched_task *waiter;
+ struct ipc_block_service_response *resp; /* Response buffer */
+ uint32_t resp_offset; /* Unused while ringbuf is disabled */
};
static spinlock_t block_pending_lock = SPINLOCK_INIT;
slot = &block_state.slots[i];
slot->in_use = true;
slot->ready = false;
+ __atomic_store_n((uint8_t *)&slot->prepared, 0, __ATOMIC_RELAXED);
+ slot->waiter = NULL;
break;
}
}
block_state.slots[i].ready = false;
block_state.slots[i].resp = NULL;
block_state.slots[i].resp_offset = 0;
+ block_state.slots[i].waiter = NULL;
+ __atomic_store_n((uint8_t *)&block_state.slots[i].prepared, 0,
+ __ATOMIC_RELAXED);
}
#ifdef LENIX_DEBUG
struct ipc_block_service_request tmp;
struct ringbuf *ringbuf;
bool warned = false;
- uint32_t cpu;
/* Suppress verbose block_service_issue logging to keep boot logs clean. */
return -1;
}
- /* Allocate response buffer from per-CPU ringbuf data pool
- * Fallback to kmem if ringbuf unavailable */
- cpu = smp_current_cpu();
- ringbuf = sched_cpu_ringbuf(cpu);
- if (ringbuf != NULL) {
- slot->resp = ringbuf_data_alloc(ringbuf,
- sizeof(*slot->resp), &slot->resp_offset);
- }
+ /*
+ * Allocate response buffer from kmem to avoid any possibility of
+ * overlap/corruption in the shared ringbuf data pool while we debug
+ * the block path. This trades some performance for safety.
+ */
+ ringbuf = NULL;
+ slot->resp = kmem_alloc(sizeof(*slot->resp));
+ slot->resp_offset = 0;
if (slot->resp == NULL) {
- /* Fallback to kmem allocation */
- slot->resp = kmem_alloc(sizeof(*slot->resp));
- slot->resp_offset = 0;
- }
- if (slot->resp == NULL) {
block_diag_log("[diag][block] response allocation failed\n");
spinlock_lock(&block_pending_lock);
slot->in_use = false;
slot->token = block_state.next_token++;
if (block_state.next_token == 0)
block_state.next_token = 1;
+ slot->waiter = sched_current_task();
tmp = *req;
tmp.token = slot->token;
if ((tmp.flags & IPC_BLOCK_SERVICE_F_WRITE) != 0 &&
block_diag_log_request(&tmp);
block_trace_record_submit(&tmp);
if (ipc_portal_send(block_state.portal, &tmp, sizeof(tmp)) != 0) {
- if (ringbuf != NULL && slot->resp_offset != 0) {
+ if (ringbuf != NULL) {
ringbuf_data_free(ringbuf, slot->resp_offset,
sizeof(*slot->resp));
- } else if (ringbuf == NULL) {
+ } else {
kmem_free(slot->resp);
}
slot->resp = NULL;
uint64_t start_ticks = timer_ticks();
uint64_t timeout_ticks = timer_get_hz() * 5; /* 5s */
+ uint64_t sleep_interval = 1; /* 10ms at 100Hz - short sleep to stay responsive */
+ size_t spins = 0;
+ static uint32_t dbg_issue_log = 0;
+ if (dbg_issue_log < 3) {
+ printk("[blocksvc] issue: dev=");
+ printk_dec((uint64_t)tmp.device);
+ printk(" lba=");
+ printk_dec(tmp.lba);
+ printk(" token=");
+ printk_dec(slot->token);
+ printk(" waiter=");
+ printk(slot->waiter ? sched_task_name(slot->waiter) : "NULL");
+ printk("\n");
+ dbg_issue_log++;
+ }
while (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE)) {
+ spins++;
if (block_state.server == NULL || slot->client == NULL ||
(timeout_ticks > 0 &&
(timer_ticks() - start_ticks) > timeout_ticks)) {
- if (ringbuf != NULL && slot->resp_offset != 0) {
+ if (ringbuf != NULL) {
ringbuf_data_free(ringbuf, slot->resp_offset,
sizeof(*slot->resp));
- } else if (ringbuf == NULL) {
+ } else {
kmem_free(slot->resp);
}
slot->resp = NULL;
slot->resp_offset = 0;
spinlock_lock(&block_pending_lock);
slot->in_use = false;
+ slot->waiter = NULL;
spinlock_unlock(&block_pending_lock);
struct ipc_block_service_response trace_resp = {
.token = slot->token,
block_diag_log("[diag][block] waiting for daemon response\n");
warned = true;
}
- sched_yield();
+ /* Yield briefly then use timed sleep to allow timeout checks */
+ if (spins < 64) {
+ sched_yield();
+ } else {
+ __atomic_store_n((uint8_t *)&slot->prepared, 1, __ATOMIC_RELEASE);
+ /* Check ready after setting prepared to avoid lost wakeup */
+ if (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE))
+ sched_sleep_ticks(sleep_interval);
+ /* Don't clear prepared here - only clear after ready=true */
+ }
}
- /* Copy response from ringbuf buffer to caller's buffer */
+ /* Copy response buffer to caller's buffer */
*resp = *slot->resp;
+ /* Detect short/partial completions even when status is success. */
+ if (resp->status == 0) {
+ uint64_t expected_bytes = (uint64_t)tmp.blocks * 512ULL;
+ if (resp->bytes_transferred != expected_bytes) {
+ printk("[blocksvc] WARN: short response bytes_transferred=");
+ printk_dec((uint64_t)resp->bytes_transferred);
+ printk(" expected=");
+ printk_dec(expected_bytes);
+ printk(" dev=");
+ printk_dec((uint64_t)tmp.device);
+ printk(" lba=");
+ printk_dec(tmp.lba);
+ printk(" blocks=");
+ printk_dec((uint64_t)tmp.blocks);
+ printk("\n");
+ }
+ }
+
/* Cleanup allocated response buffer */
- if (ringbuf != NULL && slot->resp_offset != 0) {
+ if (ringbuf != NULL) {
ringbuf_data_free(ringbuf, slot->resp_offset,
sizeof(*slot->resp));
- } else if (ringbuf == NULL) {
+ } else {
kmem_free(slot->resp);
}
slot->resp_offset = 0;
slot->in_use = false;
slot->ready = false;
+ __atomic_store_n((uint8_t *)&slot->prepared, 0, __ATOMIC_RELAXED);
+ slot->waiter = NULL;
slot->client = NULL;
spinlock_unlock(&block_pending_lock);
perf_counter_inc(PERF_COUNTER_BLOCK_RESPONSES, 1);
{
struct block_service_pending *slot;
struct sched_task *task = sched_current_task();
+ static uint32_t dbg_respond_log = 0;
+ if (dbg_respond_log < 3 && resp != NULL) {
+ printk("[blocksvc] respond: token=");
+ printk_dec(resp->token);
+ printk(" status=");
+ printk_dec((uint64_t)(uint32_t)resp->status);
+ printk(" bytes=");
+ printk_dec((uint64_t)resp->bytes_transferred);
+ printk("\n");
+ dbg_respond_log++;
+ }
+
if (!block_service_available() || resp == NULL)
return -1;
if (task != block_state.server) {
/* Publish with release semantics */
__atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
+ /* Wake the waiting client (if any) now that the response is ready.
+ * CRITICAL: Always wake if waiter exists, don't check 'prepared' flag.
+ * The prepared flag is for optimization but missing a wakeup causes hangs
+ * when responses arrive within first 64 spins before prepared is set. */
+ if (slot->waiter != NULL)
+ sched_wake(slot->waiter);
+
spinlock_unlock(&block_pending_lock);
- block_trace_record_complete(resp);
- return 0;
-}
+ block_trace_record_complete(resp);
+ return 0;
+ }
blob - c3a0f35ce9cbaf3d26450a6f4198bb84d0d1e9a2
blob + 3ec3faaf3faa6881283a3ffafe721d2ed28a18dd
--- kernel/ipc/fs_service.c
+++ kernel/ipc/fs_service.c
bool in_use;
uint32_t token;
struct sched_task *client;
+ struct sched_task *waiter;
+ volatile uint8_t prepared;
struct ipc_fs_response response;
bool ready;
};
return -1;
/* slot->in_use and slot->ready already set by fs_pending_alloc() */
slot->client = sched_current_task();
+ slot->waiter = slot->client;
+ slot->prepared = 0;
slot->token = fs_state.next_token++;
if (fs_state.next_token == 0)
fs_state.next_token = 1;
#endif
spinlock_lock(&fs_pending_lock);
slot->in_use = false;
+ slot->waiter = NULL;
spinlock_unlock(&fs_pending_lock);
return -1;
}
#endif
spinlock_lock(&fs_pending_lock);
slot->in_use = false;
+ slot->waiter = NULL;
+ __atomic_store_n(&slot->prepared, 0, __ATOMIC_RELAXED);
spinlock_unlock(&fs_pending_lock);
return -1;
}
- sched_yield();
+ /* Spin briefly then block to avoid yield storm */
+ if (spins < 256) {
+ sched_yield();
+ } else {
+ __atomic_store_n(&slot->prepared, 1, __ATOMIC_RELEASE);
+ /* Check ready after setting prepared to avoid lost wakeup */
+ if (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE))
+ sched_block_current();
+ /* Don't clear prepared here - only clear after ready=true */
+ }
}
+ /* Clear prepared after successfully receiving response */
+ __atomic_store_n(&slot->prepared, 0, __ATOMIC_RELAXED);
fs_memcpy(resp, &slot->response, sizeof(*resp));
spinlock_lock(&fs_pending_lock);
slot->in_use = false;
slot->ready = false;
+ slot->waiter = NULL;
spinlock_unlock(&fs_pending_lock);
return 0;
}
/* Copy response into slot */
fs_memcpy(&slot->response, resp, sizeof(slot->response));
+ /* Capture waiter before publishing ready */
+ struct sched_task *waiter = slot->waiter;
+ bool should_wake = (waiter != NULL &&
+ __atomic_load_n(&slot->prepared, __ATOMIC_ACQUIRE));
+
/* Publish with release semantics */
__atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
spinlock_unlock(&fs_pending_lock);
+ /* Wake blocked waiter outside lock */
+ if (should_wake)
+ sched_wake(waiter);
+
#ifdef LENIX_DEBUG
printk("[fs] respond token=");
{
blob - 9c61c131f4c3cb54b40b88f3d69cd9b3d3734e90
blob + b5783c44d4bcac545a837ae9c61a0d5e739d681f
--- kernel/ipc/mailbox.c
+++ kernel/ipc/mailbox.c
mbox->remote_bytes_sent = 0;
mbox->wait_head = NULL;
mbox->wait_tail = NULL;
+ mbox->poll_wait_head = NULL;
+ mbox->poll_wait_tail = NULL;
}
int
/* Check for blocked receivers and wake one if present */
struct sched_task *waiter = NULL;
+ struct sched_task *poll_task = NULL;
+ if (mbox->poll_wait_head != NULL) {
+ struct ipc_mailbox_poll_waiter *poll_waiter = mbox->poll_wait_head;
+ mbox->poll_wait_head = poll_waiter->next;
+ if (mbox->poll_wait_head == NULL)
+ mbox->poll_wait_tail = NULL;
+ poll_waiter->next = NULL;
+ poll_waiter->in_list = false;
+ /* Capture task pointer now; poll() may free waiter after unlock */
+ poll_task = poll_waiter->task;
+ }
if (mbox->wait_head != NULL) {
waiter = mbox->wait_head;
mbox->wait_head = sched_task_wait_next(waiter);
/* Wake the blocked receiver outside the lock */
if (waiter != NULL)
sched_wake(waiter);
+ if (poll_task != NULL)
+ sched_wake(poll_task);
perf_mailbox_depth_sample(mbox->depth);
perf_counter_inc(PERF_COUNTER_IPC_MAILBOX_ENQUEUE, 1);
}
mailbox_unlock(mbox);
+ /*
+ * Check if a message arrived while we were adding to wait queue.
+ * This closes the lost wakeup race: sender may have sent a message
+ * and called sched_wake() before we call sched_block_current().
+ */
+ if (ipc_mailbox_has_message(mbox))
+ continue; /* Message arrived, loop back to receive it */
+
/* Block until woken by sender */
sched_block_current();
}
}
+bool
+ipc_mailbox_has_message(const struct ipc_mailbox *mbox)
+{
+ bool has;
+ struct ipc_mailbox *mut = (struct ipc_mailbox *)mbox;
+
+ if (mbox == NULL)
+ return false;
+ mailbox_lock(mut);
+ has = (mut->head != NULL);
+ mailbox_unlock(mut);
+ return has;
+}
+
void
+ipc_mailbox_poll_waiter_add(struct ipc_mailbox *mbox,
+ struct ipc_mailbox_poll_waiter *waiter)
+{
+ if (mbox == NULL || waiter == NULL)
+ return;
+ mailbox_lock(mbox);
+ waiter->next = NULL;
+ waiter->in_list = true;
+ if (mbox->poll_wait_tail == NULL) {
+ mbox->poll_wait_head = waiter;
+ mbox->poll_wait_tail = waiter;
+ } else {
+ mbox->poll_wait_tail->next = waiter;
+ mbox->poll_wait_tail = waiter;
+ }
+ mailbox_unlock(mbox);
+}
+
+bool
+ipc_mailbox_poll_waiter_remove_task(struct ipc_mailbox *mbox,
+ const struct sched_task *task)
+{
+ struct ipc_mailbox_poll_waiter *prev = NULL;
+ struct ipc_mailbox_poll_waiter *cur;
+
+ if (mbox == NULL || task == NULL)
+ return false;
+ mailbox_lock(mbox);
+ cur = mbox->poll_wait_head;
+ while (cur != NULL) {
+ if (cur->task == task) {
+ if (prev != NULL)
+ prev->next = cur->next;
+ else
+ mbox->poll_wait_head = cur->next;
+ if (cur == mbox->poll_wait_tail)
+ mbox->poll_wait_tail = prev;
+ cur->next = NULL;
+ cur->in_list = false;
+ mailbox_unlock(mbox);
+ return true;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ mailbox_unlock(mbox);
+ return false;
+}
+
+void
ipc_mailbox_free(struct ipc_message *msg)
{
if (msg == NULL)
blob - 2685f96e4191af4cc97b1748b113ee3d9fef933f
blob + ea1409dc3633758f56dc308c50f8afd5f990d25c
--- kernel/ipc/portal.c
+++ kernel/ipc/portal.c
static struct ipc_portal_entry portal_table[IPC_PORTAL_MAX];
static const struct ipc_portal_transport_ops *portal_transport_ops;
static uint32_t portal_cookie_rng = 0x1f123bb5U;
+static bool portal_slot0_reserved;
/* Global portal table lock for SMP safety */
static spinlock_t portal_lock = SPINLOCK_INIT;
/* First-free search; future work can move to freelists per CPU. */
for (size_t i = 0; i < IPC_PORTAL_MAX; i++) {
+ /* Once slot 0 has been allocated, never recycle it. */
+ if (i == 0 && portal_slot0_reserved)
+ continue;
if (!portal_table[i].in_use) {
/* Warn if we're approaching table exhaustion */
if (i >= IPC_PORTAL_CRITICAL_THRESHOLD) {
printk(")\n");
}
entry = &portal_table[i];
+ if (i == 0)
+ portal_slot0_reserved = true;
break;
}
}
struct ipc_portal_entry *entry = portal_lookup(handle);
if (entry == NULL)
return -1;
+ /* Never revoke the reserved namesvc bootstrap portal (slot 0). */
+ if (((uint16_t)handle & 0xffffU) == 0)
+ return -1;
spinlock_lock(&portal_lock);
continue;
if (entry->owner != owner)
continue;
+ /* Preserve reserved namesvc portal slot 0 */
+ if ((entry->id & 0xffffU) == 0)
+ continue;
#ifdef LENIX_DEBUG_EXIT
serial_write("[portal_revoke_all] slot=",
return 0;
return entry->created_ticks;
}
+
+struct ipc_mailbox *
+ipc_portal_target_mailbox(ipc_portal_handle_t handle)
+{
+ struct ipc_portal_entry *entry = portal_lookup(handle);
+
+ if (entry == NULL)
+ return NULL;
+ return entry->target_mailbox;
+}
blob - 342823c59e7609f57d0f82f9767013e5897f4d5d
blob + 4d127416e8f6ab3c0e95a81ad911da959576cd51
--- kernel/ipc/ringbuf.c
+++ kernel/ipc/ringbuf.c
struct ringbuf_datapool *pool = &buf->datapool;
- /* Check if allocation would exceed pool size */
- if (pool->free_offset + size > pool->total_size)
- return NULL; /* Pool exhausted */
+ /*
+ * Concurrent allocations can race when multiple tasks share a CPU and
+ * take turns on the same ringbuf. Use an atomic fetch-add to reserve a
+ * unique slice and roll back on overflow so two callers never overlap.
+ */
+ uint32_t start = __atomic_fetch_add(&pool->free_offset, size,
+ __ATOMIC_ACQ_REL);
+ if (start + size > pool->total_size) {
+ /* Roll back reservation and signal exhaustion */
+ __atomic_fetch_sub(&pool->free_offset, size, __ATOMIC_ACQ_REL);
+ return NULL;
+ }
- /* Record offset before allocation */
- uint32_t offset = pool->free_offset;
-
- /* Advance allocator */
- pool->free_offset += size;
-
- /* Return pointer and offset */
- *offset_out = offset;
- return (void *)&buf->data[offset];
+ *offset_out = start;
+ return (void *)&buf->data[start];
}
/*
struct ringbuf_datapool *pool = &buf->datapool;
/* Simple optimization: reset allocator if freeing from the end */
- if (offset + size == pool->free_offset) {
- pool->free_offset = offset;
+ uint32_t expected = offset + size;
+ if (expected == __atomic_load_n(&pool->free_offset, __ATOMIC_RELAXED)) {
+ (void)__atomic_compare_exchange_n(&pool->free_offset,
+ &expected, offset, false, __ATOMIC_ACQ_REL,
+ __ATOMIC_RELAXED);
}
/* Otherwise, leak the allocation (acceptable for FIFO workloads) */
}
blob - d18cae1daceb5f4842fd3bdaf0c8df2f7057e05e
blob + ce9b1dad913836bd010d9af81b42f1247e9b613a
--- kernel/ipc/service_client.c
+++ kernel/ipc/service_client.c
#include <string.h>
#include "ipc/service_client.h"
+#include "log/printk.h"
#include "ipc/portal.h"
#include "sched/task.h"
#include "sync/spinlock.h"
+#include "time/timer.h"
/*
* Global registry for tracking pending service-to-service requests.
uint32_t token; /* Request token */
struct sched_task *waiter; /* Task waiting on the response */
struct ipc_service_pending *pending_slot; /* Pointer to pending slot in client */
+ volatile uint8_t prepared; /* Set when client is prepared to block */
};
static spinlock_t pending_requests_lock = SPINLOCK_INIT;
res = &pending_requests[i];
/* Mark as in_use under lock */
res->in_use = true;
+ __atomic_store_n((uint8_t *)&res->prepared, 0,
+ __ATOMIC_RELAXED);
break;
}
}
struct ipc_service_pending *slot;
uint8_t tmp_req[IPC_MAILBOX_MAX_PAYLOAD];
uint64_t spins = 0;
- uint64_t spin_limit = 1024; /* Default iteration budget (tuned down to avoid spin storms) */
-
- if (spin_limit_override != 0)
- spin_limit = spin_limit_override;
+ (void)spin_limit_override; /* Override no longer used; fixed threshold */
if (client == NULL || req == NULL || resp == NULL)
return -1;
/* Portal payloads are limited by IPC_MAILBOX_MAX_PAYLOAD; enforce the
return -1;
}
+ /* Debug: log service requests */
+ static uint32_t dbg_svc_issue_log = 0;
+ if (dbg_svc_issue_log < 5) {
+ printk("[svcclient] issue: token=");
+ printk_dec(slot->token);
+ printk(" portal=");
+ printk_dec((uint64_t)client->server_portal);
+ printk("\n");
+ dbg_svc_issue_log++;
+ }
+
/* Wait for response while yielding so other tasks can run */
+ uint64_t start_ticks = timer_ticks();
+ uint64_t timeout_ticks = timer_get_hz() * 5; /* 5 second timeout */
+ uint64_t sleep_interval = 1; /* 10ms at 100Hz - short sleep to stay responsive */
+
spins = 0;
while (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE)) {
- /*
- * Block after the first spin to avoid burning CPU while
- * waiting for the service to respond. The response path will
- * wake us via sched_wake() using the waiter recorded in preq.
- */
- if (spins < spin_limit / 4) {
- spins++;
+ spins++;
+
+ /* Time-based timeout */
+ if (timeout_ticks > 0 &&
+ (timer_ticks() - start_ticks) > timeout_ticks) {
+ slot->in_use = false;
+ spinlock_lock(&pending_requests_lock);
+ preq->in_use = false;
+ preq->waiter = NULL;
+ spinlock_unlock(&pending_requests_lock);
+ return -1;
+ }
+
+ /* Yield briefly then use timed sleep to allow timeout checks */
+ if (spins < 64) {
sched_yield();
- continue;
+ } else {
+ __atomic_store_n((uint8_t *)&preq->prepared, 1,
+ __ATOMIC_RELEASE);
+ /* Check ready after setting prepared to avoid lost wakeup */
+ if (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE))
+ sched_sleep_ticks(sleep_interval);
+ /* Don't clear prepared here - only clear after ready=true */
}
- sched_block_current();
}
+ /* Clear prepared after successfully receiving response */
+ __atomic_store_n((uint8_t *)&preq->prepared, 0, __ATOMIC_RELAXED);
/* Copy response to caller's buffer */
if (slot->response_len > resp_len)
struct ipc_service_pending *slot;
struct sched_task *waiter = NULL;
struct sched_task *server_task;
+ static uint32_t dbg_handle_resp_log = 0;
+ if (dbg_handle_resp_log < 5) {
+ printk("[svcclient] handle_response: token=");
+ printk_dec(token);
+ printk(" portal=");
+ printk_dec((uint64_t)portal);
+ printk("\n");
+ dbg_handle_resp_log++;
+ }
+
if (portal == IPC_PORTAL_INVALID_HANDLE || resp == NULL)
return -1;
/* Publish readiness with release semantics */
__atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
- /* Clean up the registry entry */
+ /* Capture waiter while still holding lock */
waiter = preq->waiter;
+
+ /* Clean up the registry entry */
preq->in_use = false;
preq->waiter = NULL;
spinlock_unlock(&pending_requests_lock);
- /* Wake the waiting client task (if any) now that the response is ready */
+ /* Wake the waiting client task (if any) now that the response is ready.
+ * CRITICAL: Always wake if waiter exists, don't check 'prepared' flag.
+ * The prepared flag is for optimization but missing a wakeup causes hangs
+ * when responses arrive within first 64 spins before prepared is set. */
if (waiter != NULL)
sched_wake(waiter);
blob - 146231de7d4dd46b8e2b53025e950ab300b11332
blob + ee443f40250ddcb87d1442fa37da2621c91f1e8c
--- kernel/ipc/vfs_service.c
+++ kernel/ipc/vfs_service.c
uint32_t token;
struct sched_task *client;
struct sched_task *waiter;
+ volatile uint8_t prepared;
struct ipc_vfs_response response;
bool ready;
};
slot->in_use = true;
slot->ready = false;
slot->waiter = NULL;
+ __atomic_store_n((uint8_t *)&slot->prepared, 0, __ATOMIC_RELAXED);
break;
}
}
#endif
return -1;
}
- if (spins < (spin_limit / 4)) {
+ /* Yield briefly then block to avoid yield storm (64 yields max) */
+ if (spins < 64) {
sched_yield();
} else {
- sched_block_current();
+ __atomic_store_n((uint8_t *)&slot->prepared, 1, __ATOMIC_RELEASE);
+ /* Check ready after setting prepared to avoid lost wakeup */
+ if (!__atomic_load_n(&slot->ready, __ATOMIC_ACQUIRE))
+ sched_block_current();
+ /* Don't clear prepared here - only clear after ready=true */
}
}
+ /* Clear prepared after successfully receiving response */
+ __atomic_store_n((uint8_t *)&slot->prepared, 0, __ATOMIC_RELAXED);
#ifdef LENIX_DEBUG
t_wait = __builtin_ia32_rdtsc();
#endif
slot->in_use = false;
slot->ready = false;
slot->waiter = NULL;
+ __atomic_store_n((uint8_t *)&slot->prepared, 0, __ATOMIC_RELAXED);
spinlock_unlock(&vfs_pending_lock);
#ifdef LENIX_DEBUG
t_end = __builtin_ia32_rdtsc();
/* Copy response into inline buffer */
vfs_memcpy(&slot->response, resp, sizeof(*resp));
+ /* Capture waiter before publishing ready (slot may be reused after unlock) */
+ struct sched_task *waiter = slot->waiter;
+ bool should_wake = (waiter != NULL &&
+ __atomic_load_n((uint8_t *)&slot->prepared, __ATOMIC_ACQUIRE) != 0);
+
/* Publish with release semantics */
__atomic_store_n(&slot->ready, true, __ATOMIC_RELEASE);
spinlock_unlock(&vfs_pending_lock);
/* Wake the waiting client (if any) now that the response is ready */
- if (slot->waiter != NULL)
- sched_wake(slot->waiter);
+ if (should_wake)
+ sched_wake(waiter);
return 0;
}
blob - f9f55a2bd2dcc13250def1b2a555cd10cc4b4b13
blob + f90ffd9d0af9aa3eada187217c8cbda125cc81f9
--- kernel/sched/task.c
+++ kernel/sched/task.c
#include "mm/kmem.h"
#include "mm/vm_layout.h"
#include "trap/print.h"
+#include "time/timer.h"
#include "mm/vm.h"
#include "panic.h"
#include "sched/clock.h"
uint64_t sid; /* Session ID */
uint64_t yield_count;
uint64_t block_count;
+ uint64_t sleep_deadline;
enum sched_state state;
bool is_idle;
void *stack_base;
struct sched_task *last_ipc_sender;
ipc_portal_handle_t last_ipc_portal;
struct sched_dma_allocation dma_allocs[SCHED_DMA_MAX_ALLOCATIONS];
+ struct sched_task *sleep_next;
char cwd[IPC_VFS_MAX_PATH];
size_t cwd_len;
+ bool sleeping;
+ bool pending_wake;
};
struct sched_cpu_state {
};
static struct task_exit_status exit_status_table[256];
static struct sched_task *task_list_head;
+static struct sched_task *sched_sleep_head;
+static spinlock_t sched_sleep_lock = SPINLOCK_INIT;
static void sched_task_trampoline(void) __attribute__((noreturn));
static void sched_user_launch(void *) __attribute__((noreturn));
#ifdef LENIX_DEBUG_EXIT
static void exit_status_table_dump(const char *tag);
#endif
+static void sched_wake_sleepers(uint64_t now);
#if defined(__x86_64__)
extern char __kernel_text_start[];
extern char __kernel_text_end[];
state->tick_count++;
if (state->run_length > state->max_run_length)
state->max_run_length = state->run_length;
+ sched_wake_sleepers(timer_ticks());
/* Set need_resched to trigger a context switch at a safe point */
state->need_resched = true;
/* Check for portal_recv timeouts (deadlock detection) */
if (!scheduler_started)
return;
task = sched_current_task();
+ if (task != NULL && task->pending_wake) {
+ task->pending_wake = false;
+ return;
+ }
if (task != NULL)
task->block_count++;
perf_counter_inc(PERF_COUNTER_SCHED_BLOCK, 1);
}
/* Mark task as blocked - it will NOT be requeued */
task->state = SCHED_STATE_BLOCKED;
+ /* Check pending_wake again after changing state to handle race where
+ * sched_wake() saw RUNNABLE, set pending_wake=true, but we blocked anyway */
+ if (task->pending_wake) {
+ task->pending_wake = false;
+ task->state = SCHED_STATE_RUNNABLE;
+ arch_irq_restore(flags);
+ return;
+ }
/* Switch to next runnable task without requeuing current */
sched_switch(false);
arch_irq_restore(flags);
if (task == NULL)
return;
- if (task->state != SCHED_STATE_BLOCKED)
+ if (task->sleeping)
+ (void)sched_sleep_cancel(task);
+ if (task->state != SCHED_STATE_BLOCKED) {
+ task->pending_wake = true;
return;
+ }
/* Mark as runnable */
task->state = SCHED_STATE_RUNNABLE;
+ task->pending_wake = false;
task->wait_next = NULL;
/* Add to appropriate CPU's run queue */
state->run_length++;
spinlock_unlock(&state->runq_lock);
- /* Nudge the CPU if it might be idle */
+ /* Nudge the CPU to reschedule and run the newly woken task.
+ * NOTE: This is now safe because we've eliminated the problematic
+ * interrupt-context callers (serial IRQ disabled, timer no longer
+ * calls serial_poll_rx). For same-CPU case, this just sets need_resched
+ * flag which will be checked on next schedule() call. */
sched_nudge_cpu(cpu);
}
task->wait_next = next;
}
+static void
+sched_sleep_insert_locked(struct sched_task *task, uint64_t deadline)
+{
+ struct sched_task **cur;
+
+ if (task == NULL)
+ return;
+
+ task->sleep_deadline = deadline;
+ task->sleep_next = NULL;
+ task->sleeping = true;
+
+ cur = &sched_sleep_head;
+ while (*cur != NULL && (*cur)->sleep_deadline <= deadline)
+ cur = &(*cur)->sleep_next;
+ task->sleep_next = *cur;
+ *cur = task;
+}
+
+static bool
+sched_sleep_remove_locked(struct sched_task *task)
+{
+ struct sched_task **cur;
+
+ if (task == NULL)
+ return false;
+ cur = &sched_sleep_head;
+ while (*cur != NULL) {
+ if (*cur == task) {
+ *cur = task->sleep_next;
+ task->sleep_next = NULL;
+ task->sleep_deadline = 0;
+ task->sleeping = false;
+ return true;
+ }
+ cur = &(*cur)->sleep_next;
+ }
+ return false;
+}
+
+static void
+sched_wake_sleepers(uint64_t now)
+{
+ struct sched_task *ready_head = NULL;
+ struct sched_task *ready_tail = NULL;
+
+ spinlock_lock(&sched_sleep_lock);
+ #ifdef LENIX_DEBUG
+ if (sched_sleep_head != NULL && sched_sleep_head->sleep_deadline <= now) {
+ printk("[sched_wake_sleepers] waking task, now=");
+ sched_print_uint(now);
+ printk(" deadline=");
+ sched_print_uint(sched_sleep_head->sleep_deadline);
+ printk("\n");
+ }
+ #endif
+ while (sched_sleep_head != NULL &&
+ sched_sleep_head->sleep_deadline <= now) {
+ struct sched_task *task = sched_sleep_head;
+
+ sched_sleep_head = task->sleep_next;
+ task->sleep_next = NULL;
+ task->sleeping = false;
+ if (ready_tail == NULL) {
+ ready_head = task;
+ ready_tail = task;
+ } else {
+ ready_tail->sleep_next = task;
+ ready_tail = task;
+ }
+ }
+ spinlock_unlock(&sched_sleep_lock);
+
+ while (ready_head != NULL) {
+ struct sched_task *next = ready_head->sleep_next;
+
+ ready_head->sleep_next = NULL;
+ sched_wake(ready_head);
+ ready_head = next;
+ }
+}
+
+void
+sched_sleep_ticks(uint64_t ticks)
+{
+ struct sched_task *task = sched_current_task();
+ uint64_t deadline;
+
+ if (task == NULL)
+ return;
+ if (ticks == 0) {
+ sched_yield();
+ return;
+ }
+ deadline = timer_ticks() + ticks;
+ #ifdef LENIX_DEBUG
+ static int sleep_debug_count = 0;
+ if (sleep_debug_count < 5) {
+ printk("[sched_sleep_ticks] task=");
+ printk(sched_task_name(task));
+ printk(" now=");
+ sched_print_uint(timer_ticks());
+ printk(" ticks=");
+ sched_print_uint(ticks);
+ printk(" deadline=");
+ sched_print_uint(deadline);
+ printk("\n");
+ sleep_debug_count++;
+ }
+ #endif
+ spinlock_lock(&sched_sleep_lock);
+ sched_sleep_insert_locked(task, deadline);
+ spinlock_unlock(&sched_sleep_lock);
+ sched_block_current();
+ #ifdef LENIX_DEBUG
+ static int wake_debug_count = 0;
+ if (wake_debug_count < 5) {
+ printk("[sched_sleep_ticks] woke: task=");
+ printk(sched_task_name(task));
+ printk("\n");
+ wake_debug_count++;
+ }
+ #endif
+ (void)sched_sleep_cancel(task);
+}
+
+bool
+sched_sleep_cancel(struct sched_task *task)
+{
+ bool was_sleeping;
+
+ if (task == NULL)
+ return false;
+ spinlock_lock(&sched_sleep_lock);
+ was_sleeping = sched_sleep_remove_locked(task);
+ spinlock_unlock(&sched_sleep_lock);
+ return was_sleeping;
+}
+
/*
* sched_task_trampoline - first instruction executed by every fresh task.
* Enables interrupts, runs the entrypoint, and panics on unexpected return.
serial_write("\n", 1);
}
#endif
- sched_yield();
+ sched_block_current();
}
}
if (parent_task != NULL)
(void)sched_task_signal_raise(parent_task, SIGCHLD);
+ if (parent_task != NULL)
+ sched_wake(parent_task);
}
const struct sched_credentials *
if (task == NULL)
panic("sched_exit_current: no current task");
+ (void)sched_sleep_cancel(task);
+
#ifdef LENIX_DEBUG_EXIT
serial_write("[exit] entry pid=", 17);
{
return (task != NULL) ? task->id : 0;
}
+
uint64_t
+sched_task_parent_pid(const struct sched_task *task)
+{
+ return (task != NULL) ? task->parent_pid : 0;
+
+}
+
+uint64_t
sched_task_pgrp(const struct sched_task *task)
{
return (task != NULL) ? task->pgrp : 0;
blob - 943c3555f1b7ef047fed76c00460d3ed76f483ba
blob + 14e2d19d0193143deb01bc709aa969391c47876b
--- kernel/sys/syscall.c
+++ kernel/sys/syscall.c
if (task == NULL)
return IPC_PORTAL_INVALID_HANDLE;
handle = sched_task_stdio_portal(task);
- if (handle != IPC_PORTAL_INVALID_HANDLE)
- return handle;
- handle = console_service_client_open(task);
- if (handle != IPC_PORTAL_INVALID_HANDLE)
- sched_task_set_stdio_portal(task, handle);
-#ifdef LENIX_DEBUG_EXIT
- serial_write("[stdio_portal_handle] task=",
- sizeof("[stdio_portal_handle] task=") - 1);
- syscall_debug_serial_write_u64(sched_current_task_id());
- serial_write(" handle=", sizeof(" handle=") - 1);
- syscall_debug_serial_write_u64(handle);
- serial_write("\n", 1);
-#endif
+ /*
+ * Don't auto-allocate PTYs here. If a task doesn't have a stdio portal,
+ * it should explicitly call console_open() to get one. Auto-allocating
+ * PTYs for service daemons causes their mailboxes to receive PTY input,
+ * which corrupts their IPC message streams (PTY input like "[signal %u]"
+ * gets interpreted as IPC requests with garbage opcodes).
+ *
+ * Tasks without a stdio portal will have their stdout/stderr written
+ * directly to serial as a fallback.
+ */
return handle;
}
return syscall_make_result(copied, 0);
}
+ /* Fallback: try to pull characters directly from serial if mailbox is empty.
+ * NOTE: serial_getc_nonblock() only READS characters without processing.
+ * serial_poll_rx() handles line discipline processing. */
+ /* DISABLED: Only console task should poll serial */
+ /* serial_poll_rx(); */
+ #if 1 /* Re-enable: getc no longer processes, just reads */
+ {
+ char tmpc;
+ while (copied < n && serial_getc_nonblock(&tmpc)) {
+ if (syscall_copy_to_user(buf_addr + copied, &tmpc, 1) != 0)
+ return syscall_make_result(-SYSCALL_EFAULT, 0);
+ copied++;
+ }
+ if (copied > 0)
+ return syscall_make_result(copied, 0);
+ }
+ #endif
+
if (mbox == NULL)
return syscall_make_result(-SYSCALL_EBADF, 0);
deadlock_recv_start(task, 5000);
struct ipc_vfs_response resp;
size_t req_len = n;
+#ifdef LENIX_DEBUG
+ static int read_debug_count = 0;
+ if (read_debug_count < 10) {
+ printk("[sys_read] fd=");
+ printk_dec(fd);
+ printk(" requested=");
+ printk_dec(n);
+ printk("\n");
+ read_debug_count++;
+ }
+#endif
+
syscall_memset(&req, 0, sizeof(req));
syscall_memset(&resp, 0, sizeof(resp));
if (req_len > IPC_FS_INLINE_DATA_MAX)
to_copy = IPC_FS_INLINE_DATA_MAX;
if (reported < to_copy)
to_copy = reported;
+
+#ifdef LENIX_DEBUG
+ static int read_resp_debug_count = 0;
+ if (read_resp_debug_count < 10) {
+ printk("[sys_read] fd=");
+ printk_dec(fd);
+ printk(" status=");
+ printk_dec(resp.body.read.status);
+ printk(" data_len=");
+ printk_dec(resp.body.read.data_len);
+ printk(" to_copy=");
+ printk_dec(to_copy);
+ printk("\n");
+ read_resp_debug_count++;
+ }
+#endif
+
if (syscall_copy_to_user(buf_addr, resp.body.read.data, to_copy) != 0)
return syscall_make_result(-SYSCALL_EFAULT, 0);
return syscall_make_result(to_copy, 0);
uint64_t arg5 __attribute__((unused)))
{
struct pollfd *kfds = NULL;
+ struct ipc_mailbox **poll_mboxes = NULL;
+ struct ipc_mailbox_poll_waiter **poll_waiters = NULL;
struct sched_task *task = sched_current_task();
struct signal_state *sigstate = sched_task_signal_state(task);
size_t count = (size_t)nfds;
kmem_free(kfds);
return syscall_make_result(-SYSCALL_EFAULT, 0);
}
+ poll_mboxes = kmem_zalloc(count * sizeof(*poll_mboxes));
+ poll_waiters = kmem_zalloc(count * sizeof(*poll_waiters));
+ if (poll_mboxes == NULL || poll_waiters == NULL) {
+ error = -SYSCALL_ENOMEM;
+ goto out;
+ }
}
if (sigmask_addr != 0) {
uint32_t mask;
goto out;
}
if (count == 0) {
- if (timeout <= 0)
- goto out;
- wait_ticks = sys_poll_timeout_ticks(timeout);
- while (true) {
+ if (timeout < 0) {
if (sigstate != NULL &&
signal_state_pending(sigstate) != 0) {
error = -SYSCALL_EINTR;
goto out;
}
- if (sys_poll_deadline_expired(start_ticks, wait_ticks,
- true))
- break;
- sched_yield();
+ sched_block_current();
+ } else if (timeout > 0) {
+ if (sigstate != NULL &&
+ signal_state_pending(sigstate) != 0) {
+ error = -SYSCALL_EINTR;
+ goto out;
+ }
+ wait_ticks = sys_poll_timeout_ticks(timeout);
+ if (wait_ticks > 0)
+ sched_sleep_ticks(wait_ticks);
}
+ if (sigstate != NULL &&
+ signal_state_pending(sigstate) != 0)
+ error = -SYSCALL_EINTR;
goto out;
}
infinite_wait = (timeout < 0);
have_deadline = true;
}
while (true) {
+ bool portal_present = false;
+
ready_total = 0;
for (size_t i = 0; i < count; i++) {
+ struct ipc_mailbox *mbox = NULL;
+
+ if ((kfds[i].events & POLLIN) != 0) {
+ struct ipc_mailbox *target_mbox =
+ ipc_portal_target_mailbox(
+ (ipc_portal_handle_t)kfds[i].fd);
+ struct sched_task *portal_owner =
+ ipc_portal_owner((ipc_portal_handle_t)kfds[i].fd);
+ if (target_mbox != NULL || portal_owner != NULL) {
+ /*
+ * Polling a portal FD means checking the caller's
+ * mailbox for incoming messages. The portal handle
+ * proves validity; readiness is driven by the per-task
+ * mailbox, not the portal target mailbox.
+ */
+ mbox = sched_task_mailbox(task);
+ if (mbox == NULL) {
+ kfds[i].revents = POLLERR;
+ ready_total++;
+ continue;
+ }
+ }
+ }
+ if (mbox != NULL) {
+ poll_mboxes[i] = mbox;
+ portal_present = true;
+ kfds[i].revents = 0;
+ if (ipc_mailbox_has_message(mbox)) {
+ kfds[i].revents |= POLLIN;
+ ready_total++;
+ }
+ continue;
+ }
+
rc = sys_poll_check_fd(&kfds[i]);
if (rc > 0) {
ready_total++;
}
if (ready_total > 0 || immediate_timeout)
break;
- if (sigstate != NULL &&
- signal_state_pending(sigstate) != 0) {
- interrupted_by_signal = true;
- break;
- }
- if (!infinite_wait &&
- sys_poll_deadline_expired(start_ticks, wait_ticks,
- have_deadline))
- break;
- sched_yield();
+ if (sigstate != NULL &&
+ signal_state_pending(sigstate) != 0) {
+ interrupted_by_signal = true;
+ break;
+ }
+ if (!infinite_wait &&
+ sys_poll_deadline_expired(start_ticks, wait_ticks,
+ have_deadline))
+ break;
+
+ if (portal_present) {
+ uint64_t remaining = wait_ticks;
+
+ if (have_deadline) {
+ uint64_t now_ticks = timer_ticks();
+ uint64_t elapsed = now_ticks - start_ticks;
+ if (elapsed >= wait_ticks)
+ break;
+ remaining = wait_ticks - elapsed;
+ }
+
+ for (size_t i = 0; i < count; i++) {
+ struct ipc_mailbox *mbox = poll_mboxes[i];
+
+ if (mbox == NULL || ipc_mailbox_has_message(mbox))
+ continue;
+
+ if (poll_waiters[i] == NULL) {
+ struct ipc_mailbox_poll_waiter *waiter =
+ kmem_alloc(sizeof(*waiter));
+ if (waiter == NULL)
+ continue;
+ waiter->task = task;
+ waiter->next = NULL;
+ waiter->in_list = false;
+ poll_waiters[i] = waiter;
+ ipc_mailbox_poll_waiter_add(mbox, waiter);
+ }
+ }
+
+ /* Re-check to close race after arming waiters */
+ for (size_t i = 0; i < count; i++) {
+ struct ipc_mailbox *mbox = poll_mboxes[i];
+ if (mbox == NULL)
+ continue;
+ if (ipc_mailbox_has_message(mbox)) {
+ kfds[i].revents |= POLLIN;
+ ready_total++;
+ }
+ }
+ if (ready_total > 0)
+ break;
+
+ if (have_deadline && remaining == 0)
+ break;
+ if (have_deadline && remaining > 0)
+ sched_sleep_ticks(remaining);
+ else
+ sched_block_current();
+
+ for (size_t i = 0; i < count; i++) {
+ if (poll_waiters[i] == NULL)
+ continue;
+ (void)ipc_mailbox_poll_waiter_remove_task(
+ poll_mboxes[i], task);
+ kmem_free(poll_waiters[i]);
+ poll_waiters[i] = NULL;
+ }
+ continue;
+ }
+
+ sched_yield();
}
if (syscall_copy_to_user(fds_addr, kfds, bytes) != 0) {
error = -SYSCALL_EFAULT;
out:
if (kfds != NULL)
kmem_free(kfds);
+ if (poll_waiters != NULL) {
+ for (size_t i = 0; i < count; i++) {
+ if (poll_waiters[i] != NULL) {
+ (void)ipc_mailbox_poll_waiter_remove_task(
+ (poll_mboxes != NULL) ? poll_mboxes[i] : NULL,
+ task);
+ kmem_free(poll_waiters[i]);
+ }
+ }
+ kmem_free(poll_waiters);
+ }
+ if (poll_mboxes != NULL)
+ kmem_free(poll_mboxes);
if (mask_swapped && sigstate != NULL)
signal_state_update_blocked(sigstate, SIG_SETMASK, saved_mask,
NULL);
goto out;
}
file_size = (uint64_t)vresp.body.stat.info.size;
+
+#ifdef LENIX_DEBUG
+ printk("[sys_spawn] file_size=");
+ printk_dec(file_size);
+ printk(" checking validity\n");
+#endif
+
if (file_size == 0 || file_size > (16 * 1024 * 1024)) {
#ifdef LENIX_DEBUG
printk("[sys_spawn] bad file_size=");
goto out;
}
+#ifdef LENIX_DEBUG
+ printk("[sys_spawn] allocating buffer size=");
+ printk_dec(file_size);
+ printk("\n");
+#endif
+
uint8_t *file_buf = kmem_alloc(file_size);
if (file_buf == NULL) {
+#ifdef LENIX_DEBUG
+ printk("[sys_spawn] kmem_alloc failed\n");
+#endif
rc = -SYSCALL_ENOMEM;
goto out;
}
+#ifdef LENIX_DEBUG
+ printk("[sys_spawn] buffer allocated, opening file\n");
+#endif
+
/* Open the file */
syscall_memset(&vreq, 0, sizeof(vreq));
syscall_memset(&vresp, 0, sizeof(vresp));
uint32_t prefetch_len = 0; /* Valid bytes in prefetch_buf */
uint32_t prefetch_pos = 0; /* Current position in prefetch_buf */
+#ifdef LENIX_DEBUG
+ int spawn_read_count = 0;
+#endif
+
while (offset < file_size) {
/* Refill prefetch buffer if needed */
if (prefetch_pos >= prefetch_len) {
if (to_read > 4096)
to_read = 4096;
+#ifdef LENIX_DEBUG
+ if (spawn_read_count < 10) {
+ printk("[sys_spawn read] offset=");
+ printk_dec(offset);
+ printk(" file_size=");
+ printk_dec(file_size);
+ printk(" to_read=");
+ printk_dec(to_read);
+ printk("\n");
+ spawn_read_count++;
+ }
+#endif
+
syscall_memset(&vreq, 0, sizeof(vreq));
syscall_memset(&vresp, 0, sizeof(vresp));
vreq.opcode = IPC_VFS_REQ_READ;
goto out_close;
}
prefetch_len = vresp.body.read.data_len;
+
+#ifdef LENIX_DEBUG
+ static int spawn_resp_count = 0;
+ if (spawn_resp_count < 10) {
+ printk("[sys_spawn read] resp: status=");
+ printk_dec(vresp.body.read.status);
+ printk(" data_len=");
+ printk_dec(vresp.body.read.data_len);
+ printk("\n");
+ spawn_resp_count++;
+ }
+#endif
+
if (prefetch_len == 0) {
/* EOF or short read - stop without spinning */
file_size = offset;
if (syscall_copy_to_user(status_ptr, &status, sizeof(status)) != 0)
return syscall_make_result(-SYSCALL_EFAULT, 0);
}
- if (rc > 0) {
- ipc_portal_handle_t std_handle =
- sched_task_stdio_portal(sched_current_task());
- if (std_handle != IPC_PORTAL_INVALID_HANDLE)
- (void)console_pty_rebind_handle(std_handle,
- sched_current_task());
- }
if (do_log) {
printk("[sys_waitpid] rc=");
printk_dec((int64_t)rc);
/* Issue request and wait for response */
if (ipc_service_request_issue(&client, req_buf, req_len,
resp_buf, resp_len, &actual_resp_len) != 0) {
- printk("[sys_service_request] request_issue failed portal=0x");
- {
- const char hex[] = "0123456789abcdef";
- char buf[9];
- uint32_t val = (uint32_t)portal;
- for (int shift = 28, idx = 0; shift >= 0; shift -= 4, idx++)
- buf[idx] = hex[(val >> shift) & 0xf];
- buf[8] = '\0';
- printk(buf);
+ /* Rate-limit error messages to prevent flooding serial output */
+ static uint32_t error_log_count = 0;
+ static uint64_t last_error_log_ticks = 0;
+ uint64_t now_ticks = timer_ticks();
+ /* Log first error, then at most once per second */
+ if (error_log_count == 0 ||
+ (now_ticks - last_error_log_ticks) >= timer_get_hz()) {
+ printk("[sys_service_request] request_issue failed portal=0x");
+ {
+ const char hex[] = "0123456789abcdef";
+ char buf[9];
+ uint32_t val = (uint32_t)portal;
+ for (int shift = 28, idx = 0; shift >= 0; shift -= 4, idx++)
+ buf[idx] = hex[(val >> shift) & 0xf];
+ buf[8] = '\0';
+ printk(buf);
+ }
+ printk(" req_len=");
+ printk_dec((uint64_t)req_len);
+ printk(" resp_len=");
+ printk_dec((uint64_t)resp_len);
+ printk(" src_task=");
+ printk(sched_task_name(sched_current_task()));
+ if (error_log_count > 0) {
+ printk(" (rate-limited, count=");
+ printk_dec((uint64_t)error_log_count);
+ printk(")");
+ }
+ printk("\n");
+ error_log_count++;
+ last_error_log_ticks = now_ticks;
+ } else {
+ error_log_count++;
}
- printk(" req_len=");
- printk_dec((uint64_t)req_len);
- printk(" resp_len=");
- printk_dec((uint64_t)resp_len);
- printk(" src_task=0x");
- {
- const char hex[] = "0123456789abcdef";
- char buf[17];
- uint64_t val = (uint64_t)sched_current_task();
- for (int shift = 60, idx = 0; shift >= 0 && idx < 16; shift -= 4)
- buf[idx++] = hex[(val >> shift) & 0xf];
- buf[16] = '\0';
- printk(buf);
- }
- printk("\n");
if (req_alloc)
kmem_free(req_buf);
if (resp_alloc)
uint32_t max_blocks;
uint32_t copy_len;
struct syscall_result result;
+ static uint32_t dbg_blocksvc_req_log = 0;
+ if (dbg_blocksvc_req_log < 5) {
+ printk("[sys_blocksvc_request] dev=");
+ printk_dec(device);
+ printk(" lba=");
+ printk_dec(lba);
+ printk(" blocks=");
+ printk_dec(blocks);
+ printk("\n");
+ dbg_blocksvc_req_log++;
+ }
+
if (buf_addr == 0)
return syscall_make_result(-SYSCALL_EINVAL, 0);
if (blocks == 0)
return syscall_make_result(-SYSCALL_EFAULT, 0);
}
}
+ if (resp->bytes_transferred != copy_len) {
+ printk("[blocksvc] WARN: bytes_transferred=");
+ printk_dec((uint64_t)resp->bytes_transferred);
+ printk(" expected=");
+ printk_dec((uint64_t)copy_len);
+ printk(" status=");
+ printk_dec((uint64_t)(resp->status < 0 ? -resp->status : resp->status));
+ printk("\n");
+ }
result = syscall_make_result(resp->bytes_transferred, 0);
kmem_free(req);
kmem_free(resp);
uint64_t arg4 __attribute__((unused)),
uint64_t arg5 __attribute__((unused)))
{
+ struct sched_task *task = sched_current_task();
ipc_portal_handle_t handle =
- console_service_client_open(sched_current_task());
+ console_service_client_open(task);
#if defined(DEBUG_ALL)
DEBUG_SYSCALL_LOG("[syscall] console_open task=");
DEBUG_SYSCALL_LOG(sched_current_task_name());
#endif
if (handle == IPC_PORTAL_INVALID_HANDLE)
return syscall_make_result(-SYSCALL_EBUSY, 0);
+ /*
+ * Store the console portal as the task's stdio portal so it can be
+ * inherited by child processes via posix_spawn/fork. Without this,
+ * the PTY rebind during spawn will not happen and children won't
+ * receive keyboard input.
+ */
+ sched_task_set_stdio_portal(task, handle);
return syscall_make_result(handle, 0);
}
struct console_pty_alloc_desc desc;
uint32_t pty_id;
ipc_portal_handle_t handle;
+ int rc;
if (desc_addr == 0)
return syscall_make_result(-SYSCALL_EINVAL, 0);
- if (console_pty_alloc(sched_current_task(), &pty_id, &handle) != 0) {
- printk("[pty] sys_pty_alloc: console_pty_alloc failed\n");
- return syscall_make_result(-SYSCALL_ENOMEM, 0);
+ rc = console_pty_alloc(sched_current_task(), &pty_id, &handle);
+ if (rc != 0) {
+ printk("[pty] sys_pty_alloc: console_pty_alloc failed rc=");
+ printk_dec((uint64_t)rc);
+ printk("\n");
+ /* Propagate real error code when available */
+ if (rc > 0)
+ rc = -SYSCALL_EIO;
+ return syscall_make_result((uint64_t)rc, 0);
}
desc.pty_id = pty_id;
desc.reserved = 0;
desc.handle = handle;
if (syscall_copy_to_user(desc_addr, &desc, sizeof(desc)) != 0) {
console_pty_free(pty_id, sched_current_task());
+ printk("[pty] sys_pty_alloc: copy_to_user failed\n");
return syscall_make_result(-SYSCALL_EFAULT, 0);
}
+ printk("[pty] sys_pty_alloc: returning 0 (pty_id=");
+ printk_dec((uint64_t)pty_id);
+ printk(")\n");
return syscall_make_result(0, 0);
}
uint64_t arg5 __attribute__((unused)))
{
struct sched_task *requester = sched_current_task();
+ int rc;
- if (console_pty_set_active((uint32_t)pty_id, requester) != 0)
- return syscall_make_result(-SYSCALL_EPERM, 0);
+ printk("[sys_pty_activate] entry pty_id=");
+ printk_dec((uint64_t)pty_id);
+ printk(" requester=");
+ printk(sched_task_name(requester));
+ printk("\n");
+
+ rc = console_pty_set_active((uint32_t)pty_id, requester);
+ if (rc != 0) {
+ printk("[sys_pty_activate] failed rc=");
+ printk_dec((uint64_t)(uint32_t)rc);
+ printk("\n");
+ return syscall_make_result(rc, 0);
+ }
+ printk("[sys_pty_activate] success\n");
return syscall_make_result(0, 0);
}
struct timespec req;
uint64_t hz = timer_get_hz();
uint64_t ticks;
- uint64_t start;
if (req_addr == 0)
return syscall_make_result(-SYSCALL_EINVAL, 0);
if (ticks == 0 && (req.tv_sec > 0 || req.tv_nsec > 0))
ticks = 1;
- start = timer_ticks();
- while ((timer_ticks() - start) < ticks)
- sched_yield();
+ if (ticks > 0)
+ sched_sleep_ticks(ticks);
if (rem_addr != 0) {
struct timespec zero = { 0, 0 };
blob - f66a1c51bd3c85d97b594ba61a2769d5e365e88e
blob + 32e328ce11e5916d91da513d27897ebb8eb498ba
--- kernel/time/time.c
+++ kernel/time/time.c
void
timer_handle_tick(void)
{
- serial_poll_rx();
+ /* NOTE: Removed serial_poll_rx() call - it's not safe to call from
+ * interrupt context as it can trigger complex scheduler wake operations
+ * (console_wake_service_task -> sched_wake) which can corrupt state
+ * when combined with sched_tick()'s sched_wake_sleepers().
+ * Serial polling happens from console task instead. */
timer_tick_count++;
sched_clock_tick(timer_tick_count);
sched_tick();
blob - /dev/null
blob + 1761152b588670dc718f5526553994db3140167c (mode 755)
--- /dev/null
+++ rebuild-smp.sh
+#!/bin/sh
+export TOOLCHAIN_ROOT=/opt/llvm-mercuron
+export MERCURON_SYSROOT=$PWD/third-party/build/sysroot
+export HOST_TRIPLE=x86_64-unknown-mercuron
+export KERNEL_TRIPLE=x86_64-unknown-lenix
+
+make clean
+make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$MERCURON_SYSROOT DEBUG_SMP=1 -j4
+make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$MERCURON_SYSROOT package-runtime
+make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$MERCURON_SYSROOT package-apps
+make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$MERCURON_SYSROOT initrd
+make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$MERCURON_SYSROOT efi
+make ARCH=x86_64 USE_MERCURON_TOOLCHAIN=1 MERCURON_SYSROOT=$MERCURON_SYSROOT iso
+
+
blob - b52d797730313aeb3a8a9f81be2cc0b17c57f5f1
blob + 1fffd1edd717e81d6da1bdf42984d615dea368dc
--- servers/block/blockd/main.c
+++ servers/block/blockd/main.c
static bool blockd_staged;
static volatile int blockd_terminate;
+#ifndef BLOCKD_DEBUG
+#define BLOCKD_DEBUG 0 /* Default quiet; set to 1 for verbose tracing */
+#endif
+
+#if BLOCKD_DEBUG
+#define BLOCKD_TRACE(...) printf(__VA_ARGS__)
+#else
+#define BLOCKD_TRACE(...) do { } while (0)
+#endif
+
static void
blockd_handle_sigterm(int sig __attribute__((unused)))
{
static bool blockd_backend_queue_pop(struct blockd_backend_device *slot,
struct ipc_block_service_request *req_out);
static void blockd_backend_promote_queued(struct blockd_backend_device *slot);
-static void blockd_register_with_namesvc(portal_handle_t bootstrap);
static void blockd_check_device_timeouts(void);
static uint64_t blockd_now_ms(void);
static void
blockd_log(const char *msg)
{
- /* Temporarily silence verbose blockd logs to keep boot output readable. */
- (void)msg;
+ if (!BLOCKD_DEBUG || msg == NULL)
+ return;
+ puts(msg);
}
static void
blockd_log_hex64(const char *prefix, uint64_t value)
{
#ifdef LENIX_DEBUG
+ if (!BLOCKD_DEBUG)
+ return;
const char hex[] = "0123456789abcdef";
char buf[64];
size_t idx = 0;
blockd_log_device(const struct ipc_block_info *info)
{
#ifdef LENIX_DEBUG
+ if (!BLOCKD_DEBUG)
+ return;
char buf[96];
const char prefix[] = "[blockd] device ";
size_t idx = 0;
*req_out = slot->queue[slot->queue_head];
slot->queue_head = (slot->queue_head + 1U) % BLOCKD_BACKEND_QUEUE_DEPTH;
slot->queue_len--;
+ BLOCKD_TRACE("[blockd] queue pop dev=%u token=%u lba=%llu blocks=%u len=%zu\n",
+ req_out->device, req_out->token,
+ (unsigned long long)req_out->lba,
+ req_out->blocks, slot->queue_len);
return true;
}
slot->pending_req = *req;
slot->request_pending = true;
#ifdef LENIX_DEBUG
- blockd_log_int("[blockd] dispatch request token=",
- (int)req->token);
+ BLOCKD_TRACE("[blockd] queue dispatch dev=%u token=%u lba=%llu blocks=%u flags=0x%x\n",
+ req->device, req->token, (unsigned long long)req->lba,
+ req->blocks, req->flags);
#endif
return true;
}
if (blockd_backend_queue_push(slot, req)) {
#ifdef LENIX_DEBUG
- blockd_log_int("[blockd] enqueue request token=",
- (int)req->token);
+ BLOCKD_TRACE("[blockd] queue enqueue dev=%u token=%u lba=%llu blocks=%u flags=0x%x len=%zu\n",
+ req->device, req->token, (unsigned long long)req->lba,
+ req->blocks, req->flags, slot->queue_len);
#endif
return true;
}
#ifdef LENIX_DEBUG
- blockd_log_int("[blockd] queue full token=",
- (int)req->token);
+ BLOCKD_TRACE("[blockd] queue full dev=%u token=%u\n",
+ req->device, req->token);
#endif
return false;
}
{
struct ipc_block_backend_register_response resp;
struct blockd_backend_device *slot;
+ static int register_log_count = 0;
if (req == NULL)
return;
.block_count = slot->block_count
};
blockd_log_device(&info);
+ BLOCKD_TRACE("[blockd] backend register ok dev=%u block_size=%u blocks=%llu\n",
+ slot->device_id, slot->block_size,
+ (unsigned long long)slot->block_count);
resp.device_id = slot->device_id;
resp.status = IPC_BLOCK_BACKEND_STATUS_OK;
+ if (register_log_count < 4) {
+ printf("[blockd] backend registered dev=%u block_size=%u blocks=%llu\n",
+ slot->device_id, slot->block_size,
+ (unsigned long long)slot->block_count);
+ register_log_count++;
+ }
}
if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE)
ipc_service_respond(blockd_backend_portal, resp.token,
resp.info.block_count = slot->block_count;
resp.status = IPC_BLOCK_BACKEND_STATUS_OK;
}
- if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE)
+ if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE) {
ipc_service_respond(blockd_backend_portal, resp.token,
&resp, sizeof(resp));
+ }
}
static void
{
struct ipc_block_backend_io_request resp;
struct blockd_backend_device *slot;
+ bool responded = false;
+ static int unknown_dev_log_count = 0;
if (req == NULL)
return;
+ slot = blockd_backend_find(req->device_id);
+ BLOCKD_TRACE("[blockd] backend io_request dev=%u token=%u pending=%s waiting=%s queued=%zu\n",
+ req->device_id, req->token,
+ (slot != NULL && slot->request_pending) ? "yes" : "no",
+ (slot != NULL && slot->waiting_completion) ? "yes" : "no",
+ (slot != NULL) ? slot->queue_len : 0UL);
memset(&resp, 0, sizeof(resp));
resp.opcode = IPC_BLOCK_BACKEND_OPCODE_IO_REQUEST;
resp.token = req->token;
resp.device_id = req->device_id;
resp.status = IPC_BLOCK_BACKEND_STATUS_NOT_FOUND;
- slot = blockd_backend_find(req->device_id);
if (slot != NULL && !slot->request_pending && !slot->waiting_completion)
blockd_backend_promote_queued(slot);
if (slot != NULL && slot->request_pending) {
const struct ipc_block_service_request *pending =
&slot->pending_req;
- blockd_log_int("[blockd] backend fetch device=", (int)pending->device);
+ BLOCKD_TRACE("[blockd] backend fetch dev=%u token=%u lba=%llu blocks=%u flags=0x%x\n",
+ pending->device, pending->token,
+ (unsigned long long)pending->lba,
+ pending->blocks, pending->flags);
resp.status = IPC_BLOCK_BACKEND_STATUS_OK;
resp.flags = pending->flags;
resp.lba = pending->lba;
blockd_log_int("[blockd] backend fetch token=",
(int)pending->token);
#endif
+ if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE)
+ ipc_service_respond(blockd_backend_portal, resp.token,
+ &resp, sizeof(resp));
+ responded = true;
} else if (slot != NULL && slot->waiting_completion) {
resp.status = IPC_BLOCK_BACKEND_STATUS_BUSY;
} else if (slot != NULL) {
/* Device exists but no work is queued; tell backend to back off */
+ BLOCKD_TRACE("[blockd] backend fetch idle dev=%u\n", slot->device_id);
resp.status = IPC_BLOCK_BACKEND_STATUS_IDLE;
+ } else {
+ if (unknown_dev_log_count < 8) {
+ printf("[blockd] backend fetch unknown device=%u\n",
+ req->device_id);
+ unknown_dev_log_count++;
+ }
}
- if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE)
+ if (!responded && blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE)
ipc_service_respond(blockd_backend_portal, resp.token,
&resp, sizeof(resp));
}
return;
if (!slot->waiting_completion)
return;
- blockd_log_int("[blockd] backend response token=", (int)resp_pkt->token);
+ BLOCKD_TRACE("[blockd] backend resp token=%u dev=%u status=%d bytes=%u data_len=%u\n",
+ resp_pkt->token, resp_pkt->device_id, resp_pkt->status,
+ resp_pkt->bytes_transferred, resp_pkt->data_len);
if (resp_pkt->status != IPC_BLOCK_BACKEND_STATUS_OK) {
blockd_log_hex64("[blockd] backend response error status=",
(uint64_t)resp_pkt->status);
memset(&out, 0, sizeof(out));
out.token = slot->pending_req.token;
out.status = resp_pkt->status;
- out.bytes_transferred = resp_pkt->bytes_transferred;
- out.data_len = 0;
+ out.bytes_transferred = resp_pkt->bytes_transferred;
+ out.data_len = 0;
+ if (out.status == IPC_BLOCK_BACKEND_STATUS_OK) {
+ uint64_t expect = (uint64_t)slot->pending_req.blocks * 512ULL;
+ if (out.bytes_transferred != expect) {
+ BLOCKD_TRACE("[blockd] WARN: short backend resp dev=%u token=%u bytes=%u expect=%llu\n",
+ slot->device_id, slot->pending_req.token,
+ out.bytes_transferred,
+ (unsigned long long)expect);
+ }
+ }
if ((slot->pending_req.flags & IPC_BLOCK_SERVICE_F_WRITE) == 0 &&
resp_pkt->data_len > 0) {
uint32_t copy = resp_pkt->data_len;
blockd_log("[blockd] backend service registered");
blockd_backend_portal = bootstrap;
}
- blockd_register_with_namesvc(bootstrap);
+ /* Defer namesvc registration - try once but don't block.
+ * blockd must enter its main loop immediately to process
+ * block service requests from other daemons (like ext2). */
+ static bool namesvc_registered = false;
+ {
+ portal_handle_t namesvc = service_resolve("namesvc");
+ if (namesvc != IPC_PORTAL_INVALID_HANDLE) {
+ uint32_t flags = blockd_staged ? IPC_NAMESVC_FLAG_STAGED : 0;
+ if (namesvc_register(namesvc, "blockd", bootstrap,
+ IPC_PORTAL_RIGHT_SEND, flags) == 0) {
+ blockd_log("[blockd] registered with namesvc");
+ namesvc_registered = true;
+ }
+ }
+ }
blockd_log("[blockd] service online");
static uint8_t portal_buf[BLOCKD_PORTAL_BUF_MAX];
static uint64_t last_diag_ms = 0;
+ static uint64_t last_namesvc_attempt_ms = 0;
while (1) {
if (blockd_terminate) {
return 0;
}
+ /*
+ * Block on both the client/service portal (bootstrap) and the
+ * backend portal so responses from ramdiskd/virtio-blk cannot be
+ * starved by a blocking recv on the other endpoint.
+ */
+ struct pollfd pfds[2];
+ nfds_t nfds = 0;
+
+ if (bootstrap != IPC_PORTAL_INVALID_HANDLE) {
+ pfds[nfds].fd = (int)bootstrap;
+ pfds[nfds].events = POLLIN;
+ pfds[nfds].revents = 0;
+ nfds++;
+ }
+ if (blockd_backend_portal != IPC_PORTAL_INVALID_HANDLE &&
+ blockd_backend_portal != bootstrap) {
+ pfds[nfds].fd = (int)blockd_backend_portal;
+ pfds[nfds].events = POLLIN;
+ pfds[nfds].revents = 0;
+ nfds++;
+ }
+
+ if (nfds == 0) {
+ blockd_check_device_timeouts();
+ poll(NULL, 0, 1);
+ continue;
+ }
+
+ int ready = poll(pfds, nfds, 100);
+ if (ready <= 0) {
+ blockd_check_device_timeouts();
+ continue;
+ }
+
+ for (nfds_t i = 0; i < nfds; i++) {
+ if ((pfds[i].revents & POLLIN) == 0)
+ continue;
+
+ ssize_t n = portal_recv(portal_buf, sizeof(portal_buf));
+ if (n <= 0)
+ continue;
+ if ((size_t)n > sizeof(portal_buf)) {
+ blockd_log("[blockd] portal_recv overflow; dropping message");
+ continue;
+ }
+ if ((size_t)n != sizeof(blockd_req)) {
+ blockd_handle_backend_message(portal_buf, (size_t)n);
+ continue;
+ }
+ memcpy(&blockd_req, portal_buf, sizeof(blockd_req));
+ struct blockd_backend_device *backend = blockd_backend_find(blockd_req.device);
+ BLOCKD_TRACE("[blockd] recv token=%u dev=%u lba=%llu blocks=%u flags=0x%x pending=%s waiting=%s\n",
+ blockd_req.token, blockd_req.device,
+ (unsigned long long)blockd_req.lba,
+ blockd_req.blocks, blockd_req.flags,
+ (backend != NULL && backend->request_pending) ? "yes" : "no",
+ (backend != NULL && backend->waiting_completion) ? "yes" : "no");
+ if (backend != NULL) {
+ if (!blockd_backend_queue_request(backend, &blockd_req)) {
+ BLOCKD_TRACE("[blockd] queue_request rejected dev=%u token=%u pending=%s waiting=%s\n",
+ backend->device_id, blockd_req.token,
+ backend->request_pending ? "yes" : "no",
+ backend->waiting_completion ? "yes" : "no");
+ memset(&blockd_resp, 0, sizeof(blockd_resp));
+ blockd_resp.token = blockd_req.token;
+ blockd_resp.status = -16; /* EBUSY */
+ blockd_resp.bytes_transferred = 0;
+ blockd_resp.data_len = 0;
+ block_service_respond(&blockd_resp);
+ }
+ continue;
+ }
+ BLOCKD_TRACE("[blockd] no backend for dev=%u, responding ENXIO\n",
+ blockd_req.device);
+ memset(&blockd_resp, 0, sizeof(blockd_resp));
+ blockd_resp.token = blockd_req.token;
+ blockd_resp.status = -6; /* ENXIO */
+ blockd_resp.bytes_transferred = 0;
+ blockd_resp.data_len = 0;
+ block_service_respond(&blockd_resp);
+ }
+
/* Periodic diagnostic: check for stuck devices */
uint64_t now = blockd_now_ms();
- if (now - last_diag_ms > 5000) {
- blockd_check_device_timeouts();
+
+ /* Retry namesvc registration periodically if it failed initially */
+ if (!namesvc_registered && (now - last_namesvc_attempt_ms > 100)) {
+ last_namesvc_attempt_ms = now;
+ portal_handle_t namesvc = service_resolve("namesvc");
+ if (namesvc != IPC_PORTAL_INVALID_HANDLE) {
+ uint32_t flags = blockd_staged ? IPC_NAMESVC_FLAG_STAGED : 0;
+ if (namesvc_register(namesvc, "blockd", bootstrap,
+ IPC_PORTAL_RIGHT_SEND, flags) == 0) {
+ blockd_log("[blockd] registered with namesvc (deferred)");
+ namesvc_registered = true;
+ }
+ }
+ }
+ blockd_check_device_timeouts();
+ if (now - last_diag_ms > 5000)
last_diag_ms = now;
- }
-
- ssize_t n = portal_recv(portal_buf, sizeof(portal_buf));
- if (n <= 0)
- continue;
- if ((size_t)n > sizeof(portal_buf)) {
- blockd_log("[blockd] portal_recv overflow; dropping message");
- continue;
- }
- if ((size_t)n != sizeof(blockd_req)) {
- blockd_handle_backend_message(portal_buf, (size_t)n);
- continue;
- }
- memcpy(&blockd_req, portal_buf, sizeof(blockd_req));
- struct blockd_backend_device *backend = blockd_backend_find(blockd_req.device);
- if (backend != NULL) {
- if (!blockd_backend_queue_request(backend, &blockd_req)) {
- memset(&blockd_resp, 0, sizeof(blockd_resp));
- blockd_resp.token = blockd_req.token;
- blockd_resp.status = -16; /* EBUSY */
- blockd_resp.bytes_transferred = 0;
- blockd_resp.data_len = 0;
- block_service_respond(&blockd_resp);
- }
- continue;
- }
- memset(&blockd_resp, 0, sizeof(blockd_resp));
- blockd_resp.token = blockd_req.token;
- blockd_resp.status = -6; /* ENXIO */
- blockd_resp.bytes_transferred = 0;
- blockd_resp.data_len = 0;
- block_service_respond(&blockd_resp);
}
}
uint64_t wait_ms = now - slot->waiting_since_ms;
if (wait_ms > BLOCKD_BACKEND_TIMEOUT_MS) {
/* Backend timed out - clear stuck state and fail the request */
- printf("[blockd] TIMEOUT dev=%u waited=%llu ms, clearing stuck state\n",
+ BLOCKD_TRACE("[blockd] TIMEOUT dev=%u waited=%llu ms, clearing stuck state\n",
slot->device_id, (unsigned long long)wait_ms);
struct ipc_block_service_response out;
blockd_backend_promote_queued(slot);
} else if (wait_ms > 2000) {
/* Log warning if waiting more than 2s */
- printf("[blockd] WAITING dev=%u for %llu ms token=%u\n",
+ BLOCKD_TRACE("[blockd] WAITING dev=%u for %llu ms token=%u\n",
slot->device_id, (unsigned long long)wait_ms,
slot->pending_req.token);
}
}
}
-
-static void
-blockd_register_with_namesvc(portal_handle_t bootstrap)
-{
- const int max_attempts = 1000;
- bool ns_wait_logged = false;
- for (int attempt = 0; attempt < max_attempts; attempt++) {
- portal_handle_t namesvc = service_resolve("namesvc");
-
- if (namesvc != IPC_PORTAL_INVALID_HANDLE) {
- uint32_t flags = blockd_staged ? IPC_NAMESVC_FLAG_STAGED : 0;
- int rc = namesvc_register(namesvc, "blockd", bootstrap,
- IPC_PORTAL_RIGHT_SEND, flags);
- if (rc == 0) {
- blockd_log("[blockd] registered with namesvc");
- return;
- }
- /* Registration failed; retry */
- }
- /* Only log once after 100 iterations if still waiting */
- if (attempt == 100 && !ns_wait_logged) {
- blockd_log("[blockd] namesvc registration pending...");
- ns_wait_logged = true;
- }
- poll(NULL, 0, 1);
- }
- blockd_log("[blockd] WARN: namesvc registration timed out");
-}
blob - d1c4d5e4aeee461471f0ef637db351c94dc009f7
blob + dd1a43a66b91b17e300b153ed564aeaae063c7c8
--- servers/block/ramdiskd/main.c
+++ servers/block/ramdiskd/main.c
#include <ipc/block_backend.h>
#include <ipc/block_protocol.h>
+#ifndef RAMDISKD_DEBUG
+#define RAMDISKD_DEBUG 0
+#endif
+
+#if RAMDISKD_DEBUG
+#define RAMDISKD_TRACE(...) printf(__VA_ARGS__)
+#define RAMDISKD_DEBUG_ENABLED 1
+#else
+#define RAMDISKD_TRACE(...) do { } while (0)
+#define RAMDISKD_DEBUG_ENABLED 0
+#endif
+
#define RAMDISK_BLOCK_SIZE 512U
#define RAMDISK_PAGE_SIZE 4096U
#define RAMDISK_MAX_BYTES (64U * 1024U * 1024U)
}
DLOG("[ramdiskd] rootfs_info succeeded");
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_line("[ramdiskd] initrd handoff validated successfully");
log_hex64("[ramdiskd] phys_base ", info.phys_base);
log_hex64("[ramdiskd] phys_length ", info.length);
log_hex64("[ramdiskd] rootfs_alias @ ", (uint64_t)(uintptr_t)rootfs_alias);
- #endif
+#endif
uint64_t phys_base = info.phys_base & ~(uint64_t)(RAMDISK_PAGE_SIZE - 1ULL);
uint64_t offset = info.phys_base - phys_base;
map_len = (map_len + (RAMDISK_PAGE_SIZE - 1ULL)) &
~(uint64_t)(RAMDISK_PAGE_SIZE - 1ULL);
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_hex64("[ramdiskd] page-aligned phys_base ", phys_base);
log_hex64("[ramdiskd] map_len (bytes) ", map_len);
log_hex64("[ramdiskd] alias offset ", offset);
- #endif
+#endif
DLOG("[ramdiskd] about to call hw_mmio_map...");
if (hw_mmio_map((uint64_t)(uintptr_t)rootfs_alias, phys_base, map_len,
HWIO_MAP_READ | HWIO_MAP_WRITE) != 0) {
return -1;
}
DLOG("[ramdiskd] hw_mmio_map succeeded");
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_line("[ramdiskd] initrd mapped successfully");
- #endif
+#endif
ramdisk_alias_offset = offset;
ramdisk_length = info.length;
ramdisk_block_count = (uint32_t)((ramdisk_length + RAMDISK_BLOCK_SIZE - 1ULL) /
RAMDISK_BLOCK_SIZE);
DLOG("[ramdiskd] ramdisk_block_count = ");
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_dec64(NULL, ramdisk_block_count);
const uint8_t *sample = rootfs_alias + ramdisk_alias_offset + 0x438;
uint16_t sample_magic = 0;
return 0;
}
-#ifdef LENIX_DEBUG
-static void
-log_hex32(const char *prefix, uint32_t value)
-{
- char buf[64];
- const char hex[] = "0123456789abcdef";
- size_t idx = 0;
+// #ifdef LENIX_DEBUG
+// static void
+// log_hex32(const char *prefix, uint32_t value)
+// {
+// char buf[64];
+// const char hex[] = "0123456789abcdef";
+// size_t idx = 0;
- if (prefix != NULL) {
- for (size_t p = 0; prefix[p] != '\0' &&
- idx < sizeof(buf) - 1; p++)
- buf[idx++] = prefix[p];
- }
- if (idx < sizeof(buf) - 1)
- buf[idx++] = '0';
- if (idx < sizeof(buf) - 1)
- buf[idx++] = 'x';
- for (int shift = 28; shift >= 0 && idx < sizeof(buf) - 1;
- shift -= 4)
- buf[idx++] = hex[(value >> shift) & 0xF];
- buf[idx] = '\0';
- puts(buf);
-}
-#endif
+// if (prefix != NULL) {
+// for (size_t p = 0; prefix[p] != '\0' &&
+// idx < sizeof(buf) - 1; p++)
+// buf[idx++] = prefix[p];
+// }
+// if (idx < sizeof(buf) - 1)
+// buf[idx++] = '0';
+// if (idx < sizeof(buf) - 1)
+// buf[idx++] = 'x';
+// for (int shift = 28; shift >= 0 && idx < sizeof(buf) - 1;
+// shift -= 4)
+// buf[idx++] = hex[(value >> shift) & 0xF];
+// buf[idx] = '\0';
+// puts(buf);
+// }
+// #endif
static void
ramdisk_handle_request(const struct ipc_block_backend_io_request *req,
resp->bytes_transferred = read_bytes;
ramdisk_stats.read_bytes += read_bytes;
}
+ if (resp->status == IPC_BLOCK_BACKEND_STATUS_OK &&
+ resp->bytes_transferred != bytes) {
+ log_hex64("[ramdiskd] WARN short io bytes=", resp->bytes_transferred);
+ log_hex64("[ramdiskd] expected bytes=", bytes);
+ log_hex64("[ramdiskd] device ", req->device_id);
+ log_hex64("[ramdiskd] lba ", req->lba);
+ log_hex64("[ramdiskd] blocks ", req->blocks);
+ }
ramdisk_stats.requests_completed++;
}
}
}
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_line("[ramdiskd] initializing service client...");
- #endif
+#endif
if (ipc_service_client_init(&client, portal) != 0) {
DLOG("[ramdiskd] FATAL: ipc_service_client_init failed");
log_line("[ramdiskd] client init failed");
return -1;
}
DLOG("[ramdiskd] ipc_service_client_init succeeded");
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_line("[ramdiskd] client initialized");
log_line("[ramdiskd] mapping rootfs...");
- #endif
+#endif
if (ramdisk_map_rootfs() != 0) {
DLOG("[ramdiskd] WARNING: rootfs mapping failed - yielding backend slot");
log_line("[ramdiskd] WARNING: ramdisk_map_rootfs failed; backend not registered");
disable_backend = true;
} else {
DLOG("[ramdiskd] loader-provided rootfs mapped successfully");
- #ifdef LENIX_DEBUG
- log_line("[ramdiskd] loader-provided rootfs mapped successfully");
- #endif
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
+ log_line("[ramdiskd] loader-provided rootfs mapped successfully");
+#endif
}
/* Register with blockd */
return 0;
}
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_line("[ramdiskd] registering backend device...");
- #endif
+#endif
DLOG("[ramdiskd] sending REGISTER request...");
reg_req.opcode = IPC_BLOCK_BACKEND_OPCODE_REGISTER;
reg_req.block_size = RAMDISK_BLOCK_SIZE;
device_id = reg_resp.device_id;
DLOG("[ramdiskd] SUCCESS: registered as device_id = ");
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_dec64(NULL, device_id);
log_line("[ramdiskd] registered backend device");
log_hex32("[ramdiskd] device_id=", (uint32_t)device_id);
- #endif
+#endif
memset(&fetch, 0, sizeof(fetch));
memset(&work, 0, sizeof(work));
/* MAIN SERVICE LOOP */
DLOG("[ramdiskd] entering main service loop - ready for I/O");
- #ifdef LENIX_DEBUG
+#if defined(LENIX_DEBUG) && RAMDISKD_DEBUG_ENABLED
log_line("[ramdiskd] backend ready for I/O");
- #endif
+#endif
uint32_t busy_counter = 0; /* Track consecutive BUSY responses */
uint64_t busy_start_ms = 0; /* Timestamp when BUSY streak started */
if (fetch.token == 0)
fetch.token = 1;
- if (ipc_service_request_issue(&client, &fetch, sizeof(fetch),
- &work, sizeof(work)) != 0) {
- DLOG("[ramdiskd] ipc_service_request_issue failed - retrying");
- poll(NULL, 0, 1);
+ RAMDISKD_TRACE("[ramdiskd] fetch->blockd token=%u dev=%u lba=%llu blocks=%u\n",
+ fetch.token, fetch.device_id,
+ (unsigned long long)fetch.lba, fetch.blocks);
+
+ /* Fetch work from blockd with timeout. If this blocks for too long,
+ * it could indicate a deadlock (blockd waiting for ramdiskd response
+ * while ramdiskd is waiting for blockd fetch response). */
+ int fetch_rc = ipc_service_request_issue(&client, &fetch, sizeof(fetch),
+ &work, sizeof(work));
+ if (fetch_rc != 0) {
+ /* Log fetch failures to detect deadlocks */
+ static uint32_t fetch_error_count = 0;
+ fetch_error_count++;
+ if (fetch_error_count <= 5 || (fetch_error_count % 100) == 0) {
+ log_line("[ramdiskd] WARN: fetch ipc error (timeout or deadlock?)");
+ }
+ /* On fetch failure, sleep briefly and retry to avoid tight loop */
+ poll(NULL, 0, 10); /* Sleep 10ms before retry */
continue;
}
+ RAMDISKD_TRACE("[ramdiskd] fetch status=%d dev=%u lba=%llu blocks=%u token=%u\n",
+ work.status, work.device_id,
+ (unsigned long long)work.lba, work.blocks,
+ work.token);
- if (work.status != IPC_BLOCK_BACKEND_STATUS_OK) {
- if (work.status == IPC_BLOCK_BACKEND_STATUS_IDLE) {
- /*
- * IDLE = no work pending at all.
- * Back off moderately since there's nothing to do.
- * Reset BUSY timeout since this is normal idle state.
- * Use 10ms (not 100ms) to stay responsive during boot.
- */
- busy_counter = 0;
- busy_start_ms = 0;
- busy_timeout_logged = false;
- poll(NULL, 0, 10); /* 10ms backoff when idle */
- } else if (work.status == IPC_BLOCK_BACKEND_STATUS_BUSY) {
- /*
- * BUSY = blockd is waiting for completion of prior request.
+ if (work.status != IPC_BLOCK_BACKEND_STATUS_OK) {
+ if (work.status == IPC_BLOCK_BACKEND_STATUS_IDLE) {
+ /*
+ * IDLE = no work pending at all.
+ * Sleep for 10ms to reduce CPU contention and allow console task
+ * to poll serial input frequently. This still maintains reasonable
+ * latency for block requests while giving other tasks CPU time.
+ */
+ busy_counter = 0;
+ busy_start_ms = 0;
+ busy_timeout_logged = false;
+ poll(NULL, 0, 10); /* Sleep 10ms to reduce CPU contention */
+ idle_poll_count++;
+ continue;
+ } else if (work.status == IPC_BLOCK_BACKEND_STATUS_BUSY) {
+ /*
+ * BUSY = blockd is waiting for completion of prior request.
* Use adaptive back-off:
* - Tier 1 (first 100): 1ms sleep
* - Tier 2 (100-1000): 5ms sleep
}
/* Adaptive sleep based on busy counter */
- if (busy_counter < RAMDISK_BUSY_TIER1_THRESHOLD) {
- poll(NULL, 0, 1);
- } else if (busy_counter < RAMDISK_BUSY_TIER2_THRESHOLD) {
- ramdisk_stats.backoff_steps++;
- poll(NULL, 0, 5);
- } else {
- ramdisk_stats.backoff_steps++;
- poll(NULL, 0, 10);
- }
+ int sleep_ms = 1;
+ if (busy_counter > RAMDISK_BUSY_TIER2_THRESHOLD)
+ sleep_ms = 10;
+ else if (busy_counter > RAMDISK_BUSY_TIER1_THRESHOLD)
+ sleep_ms = 5;
+ poll(NULL, 0, sleep_ms);
+ continue;
} else if (work.status == IPC_BLOCK_BACKEND_STATUS_NOT_FOUND) {
- /* Real error - device was deregistered */
- log_line("[ramdiskd] ERROR: blockd reports device not found");
- break;
+ /* Device not found - could be a transient error or device was deregistered.
+ * Instead of exiting immediately, log and retry after a delay.
+ * This handles race conditions during device registration/deregistration. */
+ static uint32_t not_found_count = 0;
+ not_found_count++;
+ if (not_found_count <= 5 || (not_found_count % 100) == 0) {
+ log_line("[ramdiskd] WARN: blockd reports device not found (will retry)");
+ }
+ /* Retry after a short delay - device might be re-registering */
+ poll(NULL, 0, 10); /* Sleep 10ms before retry */
+ continue;
} else {
- /* Other error status - log and throttle repeated messages */
- if ((idle_poll_count - last_error_log_time) > 1000000) {
- log_line("[ramdiskd] WARN: blockd I/O fetch error, will retry");
- last_error_log_time = idle_poll_count;
+ /* Other error status - log and throttle repeated messages */
+ if ((idle_poll_count - last_error_log_time) > 1000000) {
+ log_line("[ramdiskd] WARN: blockd I/O fetch error, will retry");
+ last_error_log_time = idle_poll_count;
+ }
+ poll(NULL, 0, 1); /* Sleep 1ms on unknown errors */
}
- poll(NULL, 0, 1);
+ idle_poll_count++;
+ continue;
}
- idle_poll_count++;
- continue;
- }
- /* Reset counters when we get actual work */
- busy_counter = 0;
- busy_start_ms = 0;
+ /* Reset counters when we get actual work */
+ busy_counter = 0;
+ busy_start_ms = 0;
busy_timeout_logged = false;
idle_poll_count = 0;
response.token = work.token ? work.token : fetch.token;
ramdisk_handle_request(&work, &response);
- /* Send response back to blockd */
- if (ipc_service_request_issue(&client, &response,
- sizeof(response), &ack, sizeof(ack)) != 0) {
+ /* Send response back to blockd via portal_send (not service_request).
+ * blockd receives backend messages via portal_recv(), not service_request handler. */
+ if (portal_send(client.server_portal, &response, sizeof(response)) != 0) {
DLOG("[ramdiskd] failed to send response - continuing anyway");
continue;
}
blob - 0075880ee1c40df4d583455662c3ac7a8a0d0e2c
blob + 981e866d81b82e36ca71707f3bdb21b05475a21e
--- servers/console/ptyctl/main.c
+++ servers/console/ptyctl/main.c
(void)envp;
bootstrap = portal_get_bootstrap();
- if (bootstrap == 0) {
- puts("[ptyctl] bootstrap handle invalid");
- return 1;
- }
- if (console_ttyctl_register((portal_handle_t)bootstrap) != 0) {
- puts("[ptyctl] console_ttyctl_register failed");
+ if (bootstrap == 0) {
+ puts("[ptyctl] bootstrap handle invalid");
return 1;
}
+ {
+ int rc = console_ttyctl_register((portal_handle_t)bootstrap);
+ if (rc < 0) {
+ printf("[ptyctl] console_ttyctl_register failed rc=%d errno=%d\n",
+ rc, errno);
+ return 1;
+ }
+ if (rc > 0)
+ printf("[ptyctl] console_ttyctl_register unexpected rc=%d (continuing)\n", rc);
+ }
if (staged)
(void)srv_stage_start(SRV_ID_PTYCTL, 0, &gen);
ptyctl_staged = staged;
blob - 6d8d00160779966413ae394d1c0fabfbac02a199
blob + 04442e0ecfad76e527d911a61816dc105d3c562b
--- servers/console/ttyd/main.c
+++ servers/console/ttyd/main.c
#include <stddef.h>
#include <lenix/unistd.h>
#include <lenix/libc.h>
+#include <lenix/errno.h>
#include <lenix/ipc.h>
#include <lenix/console.h>
#include <lenix/pty.h>
puts("[ttyd] invalid bootstrap handle");
return 1;
}
- if (console_tty_register(bootstrap) != 0) {
+ int reg_rc = console_tty_register(bootstrap);
+ if (reg_rc != 0 && errno != EBUSY) {
+ /* EBUSY means kernel already pre-registered us - that's OK */
puts("[ttyd] console_tty_register failed");
return 1;
}
(void)srv_stage_start(SRV_ID_TTYD, 0, &gen);
ttyd_staged = staged;
#ifdef LENIX_DEBUG
- puts("[ttyd] console_tty_register ok");
+ if (reg_rc == 0)
+ puts("[ttyd] console_tty_register ok");
+ else
+ puts("[ttyd] console_tty_register skipped (kernel pre-registered)");
#endif
{
blob - 1a9d1478349d730bee04745f857a905e9654d1b9
blob + 924031f1060d6c236bf3817eb6f7ba70fce9f0dd
--- servers/fs/ext2/main.c
+++ servers/fs/ext2/main.c
#include <lenix/service.h>
#include <lenix/namesvc_client.h>
#include <lenix/dirent.h>
+#include <lenix/signal.h>
#include <stdbool.h>
#include <stdint.h>
uint32_t inode_no, uint32_t offset, void *buf, size_t len);
static int ext2_read_symlink_target(const struct ext2_inode *inode,
uint32_t inode_no, char *out, size_t cap, uint32_t *out_len);
+static void ext2_log_request(const struct ipc_fs_request *req);
+static void ext2_sig_handler(int sig);
static int block_read_sectors(uint64_t lba, uint32_t count, void *buf)
{
/*
* Batch read multiple sectors per IPC call to reduce overhead.
- * Can fit up to 8 sectors (4096B / 512B) per blocksvc call.
+ * Can fit up to 8 sectors (4096B / 512B) per blocksvc call. If the
+ * backend returns a short transfer, keep issuing requests until the
+ * requested range is filled or an error occurs.
*/
while (remaining > 0) {
uint32_t batch_size = remaining;
if (batch_size > 8)
batch_size = 8;
+ size_t expected_bytes = (size_t)batch_size * EXT2_SECTOR_SIZE;
ssize_t rc = block_service_rw(ext2_device_id, lba, dst,
batch_size, 0);
- if (rc != (ssize_t)(batch_size * EXT2_SECTOR_SIZE)) {
+ if (rc < 0) {
printf("[ext2] block_read_sectors: lba=%llu batch=%u rc=%zd\n",
(unsigned long long)lba, batch_size, rc);
return -1;
}
- lba += batch_size;
- dst += batch_size * EXT2_SECTOR_SIZE;
- remaining -= batch_size;
+ if ((size_t)rc > expected_bytes) {
+ printf("[ext2] block_read_sectors: lba=%llu batch=%u rc=%zd (oversize)\n",
+ (unsigned long long)lba, batch_size, rc);
+ return -1;
+ }
+ if (rc == 0 || (rc % EXT2_SECTOR_SIZE) != 0) {
+ printf("[ext2] block_read_sectors: short/unaligned rc=%zd lba=%llu batch=%u\n",
+ rc, (unsigned long long)lba, batch_size);
+ return -1;
+ }
+ uint32_t read_sectors = (uint32_t)(rc / EXT2_SECTOR_SIZE);
+ if (read_sectors > batch_size)
+ read_sectors = batch_size;
+ lba += read_sectors;
+ dst += (size_t)read_sectors * EXT2_SECTOR_SIZE;
+ remaining -= read_sectors;
}
return 0;
}
return -1;
offset = (uint64_t)block_no * ext2_ctx.block_size;
limit_bytes = ext2_device_block_size * ext2_device_block_count;
- if (offset > limit_bytes || offset + ext2_ctx.block_size > limit_bytes) {
- ext2_log_block_error("invalid block", block_no);
+
+ /* Check if block is valid according to filesystem superblock first.
+ * The filesystem superblock knows the actual filesystem size, so we should
+ * trust it over device capacity checks. If the filesystem says the block
+ * exists, it should be valid (the filesystem was created on this device). */
+ if (ext2_ctx.super.s_blocks_count != 0 && block_no >= ext2_ctx.super.s_blocks_count) {
+ ext2_log_block_error("invalid block (exceeds filesystem)", block_no);
return -1;
}
+
+ /* Then check device capacity as a safety measure. However, if the filesystem
+ * superblock says the block is valid, we should allow it even if it's close
+ * to the device limit (filesystem overhead, rounding, etc.). Only reject if
+ * it's clearly beyond device capacity. */
+ if (offset >= limit_bytes) {
+ ext2_log_block_error("invalid block (exceeds device)", block_no);
+ return -1;
+ }
+ /* If block extends slightly beyond device due to filesystem block size
+ * being larger than device sector size, read what we can and zero-fill. */
+ if (offset + ext2_ctx.block_size > limit_bytes) {
+ uint64_t available = limit_bytes - offset;
+ if (available == 0) {
+ ext2_log_block_error("invalid block (no space)", block_no);
+ return -1;
+ }
+ /* Read partial block - zero-fill the rest */
+ if (ext2_read_bytes(offset, buf, (size_t)available) != 0)
+ return -1;
+ memset((uint8_t *)buf + available, 0, ext2_ctx.block_size - (size_t)available);
+ return 0;
+ }
return ext2_read_bytes(offset, buf, ext2_ctx.block_size);
}
ext2_read_block_with_wait(uint32_t block_no, void *buf)
{
for (int attempt = 0; attempt < EXT2_BLOCK_WAIT_RETRIES; attempt++) {
- if (ext2_read_block(block_no, buf) == 0)
+ int rc = ext2_read_block(block_no, buf);
+ if (rc == 0)
return 0;
+#ifdef LENIX_DEBUG
+ if (attempt == 0 || attempt < 5 || (attempt % 1000) == 0) {
+ printf("[ext2] block read retry block=%u attempt=%d rc=%d\n",
+ block_no, attempt + 1, rc);
+ }
+#endif
if (attempt == 0)
puts("[ext2] waiting for block service...");
for (int spin = 0; spin < EXT2_BLOCK_WAIT_SPIN; spin++) {
size_t chunk = (block_remaining < len) ? block_remaining : len;
temp = temp_block;
+ /* Validate block_off and chunk to prevent buffer overflow */
+ if (block_off >= EXT2_MAX_BLOCK_SIZE || chunk > EXT2_MAX_BLOCK_SIZE ||
+ block_off + chunk > EXT2_MAX_BLOCK_SIZE) {
+ printf("[ext2] write_bytes: invalid block_off=%zu chunk=%zu\n",
+ block_off, chunk);
+ return -1;
+ }
+
if (block_off == 0 && chunk == ext2_ctx.block_size) {
/* Aligned full block write - no RMW needed */
uint64_t sector = block_start / EXT2_SECTOR_SIZE;
continue;
}
/* Partial sector write - need RMW */
+ /* Validate sector_off and chunk to prevent buffer overflow */
+ if (sector_off >= EXT2_SECTOR_SIZE || chunk > EXT2_SECTOR_SIZE ||
+ sector_off + chunk > EXT2_SECTOR_SIZE) {
+ printf("[ext2] write_bytes: invalid sector_off=%zu chunk=%zu\n",
+ sector_off, chunk);
+ return -1;
+ }
if (block_read_sectors(sector, 1, temp) != 0)
return -1;
memcpy(temp + sector_off, in, chunk);
#endif
#ifdef LENIX_DEBUG
uint32_t gd_block = (ext2_ctx.block_size == 1024) ? 2U : 1U;
- // puts("[ext2] reading group descriptor block...");
+ puts("[ext2] reading group descriptor block...");
+ {
+ char buf[96];
+ snprintf(buf, sizeof(buf),
+ "[ext2] group descriptor block=%u", gd_block);
+ puts(buf);
+ }
if (ext2_read_block_with_wait(gd_block, blk) != 0) {
// puts("[ext2] failed to read group descriptor");
return -1;
}
struct ext2_group_desc *gd = (struct ext2_group_desc *)blk;
ext2_ctx.inode_table_block = gd->bg_inode_table;
+ {
+ char buf[128];
+ snprintf(buf, sizeof(buf),
+ "[ext2] group descriptor read ok inode_table_block=%u",
+ ext2_ctx.inode_table_block);
+ puts(buf);
+ }
puts("[ext2] mount complete");
#else
if (ext2_read_block_with_wait((ext2_ctx.block_size == 1024) ? 2U : 1U, blk) != 0)
static int
ext2_is_valid_block_no(uint32_t block)
{
- if (block >= ext2_device_block_count)
+ /* Check against ext2 filesystem block count (not device block count) */
+ if (ext2_ctx.super.s_blocks_count != 0 &&
+ block >= ext2_ctx.super.s_blocks_count)
return 0;
/* Overflow check: ensure block * block_size doesn't overflow */
if (block > (UINT64_MAX / ext2_ctx.block_size))
return 0;
uint64_t byte_offset = (uint64_t)block * ext2_ctx.block_size;
+ /* Also check that byte offset doesn't exceed device capacity */
if (byte_offset > ext2_device_block_size * ext2_device_block_count)
return 0;
return 1;
struct ext2_inode *inode_out, uint32_t dir_inode_no)
{
uint8_t blk[EXT2_MAX_BLOCK_SIZE];
+ #ifdef LENIX_DEBUG
+ char comp[64];
+ size_t nlen = name_len;
+ if (nlen >= sizeof(comp))
+ nlen = sizeof(comp) - 1;
+ memcpy(comp, name, nlen);
+ comp[nlen] = '\0';
+ printf("[ext2] dir_lookup: ino=%u name='%s'\n", dir_inode_no, comp);
+ #endif
uint32_t max_blocks = 12;
if (dir_inode->i_size / ext2_ctx.block_size < 12)
max_blocks = (dir_inode->i_size + ext2_ctx.block_size - 1) /
}
path_copy[path_len] = '\0';
}
+ #ifdef LENIX_DEBUG
+ printf("[ext2] lookup path='%s'\n", (path != NULL) ? path_copy : "(null)");
+ #endif
+ #ifdef LENIX_DEBUG
+ puts("[ext2] lookup: reading root inode");
+ #endif
if (ext2_read_inode(2, &inode) != 0)
return -1;
+ #ifdef LENIX_DEBUG
+ puts("[ext2] lookup: root inode read ok");
+ #endif
uint32_t current_inode_no = 2;
if (path == NULL || path[0] == '\0') {
if (inode_out)
size_t len = (size_t)(p - start);
struct ext2_inode next;
uint32_t child_inode;
+ #ifdef LENIX_DEBUG
+ printf("[ext2] lookup: component '%.*s' parent_ino=%u\n",
+ (int)len, start, current_inode_no);
+ #endif
if ((inode.i_mode & S_IFMT) != S_IFDIR)
return -1;
if (ext2_dir_lookup(&inode, start, len, &child_inode, &next,
puts(buf);
return -1;
}
+ #ifdef LENIX_DEBUG
+ printf("[ext2] lookup: found ino=%u\n", child_inode);
+ #endif
inode = next;
current_inode_no = child_inode;
while (*p == '/')
int idx = fd - EXT2_FD_BASE;
if (idx < 0 || idx >= EXT2_MAX_HANDLES)
return NULL;
- if (!ext2_handles[idx].in_use)
+ struct ext2_handle *h = &ext2_handles[idx];
+ /* Validate handle pointer is within expected range */
+ if ((uintptr_t)h < (uintptr_t)ext2_handles ||
+ (uintptr_t)h >= (uintptr_t)(ext2_handles + EXT2_MAX_HANDLES)) {
+ printf("[ext2] ext2_handle_lookup: handle pointer out of range: %p\n", (void*)h);
return NULL;
- return &ext2_handles[idx];
+ }
+ if (!h->in_use)
+ return NULL;
+ return h;
}
static void
path[len] = '\0';
#ifdef LENIX_DEBUG
puts("[ext2] fs_handle_stat called");
+ printf("[ext2] stat path_len=%zu path='%s'\n", len, path);
#endif
fs_handle_stat_path(path, req->body.stat.flags, resp);
}
return;
}
+ /* Validate handle pointer is reasonable (not a small integer) */
+ if ((uintptr_t)h < 0x1000) {
+ printf("[ext2] fs_handle_seek: invalid handle pointer %p\n", (void*)h);
+ resp->body.seek.status = EXT2_EBADF;
+ return;
+ }
+
+ /* Validate handle is still in use */
+ if (!h->in_use) {
+ printf("[ext2] fs_handle_seek: handle not in use\n");
+ resp->body.seek.status = EXT2_EBADF;
+ return;
+ }
+
if (h->type == EXT2_HANDLE_FILE) {
int64_t new_off = (int64_t)h->u.file.offset;
switch (req->body.seek.whence) {
new_off += req->body.seek.offset;
break;
case SEEK_END:
+ /* Validate inode pointer before accessing i_size */
+ if ((uintptr_t)&h->u.file.inode < 0x1000) {
+ printf("[ext2] fs_handle_seek: invalid inode pointer %p\n", (void*)&h->u.file.inode);
+ resp->body.seek.status = EXT2_EBADF;
+ return;
+ }
new_off = h->u.file.inode.i_size + req->body.seek.offset;
break;
default:
}
if (new_off < 0)
new_off = 0;
- if ((uint64_t)new_off > h->u.file.inode.i_size)
- new_off = h->u.file.inode.i_size;
+ /* Validate inode pointer before accessing i_size again */
+ if ((uintptr_t)&h->u.file.inode >= 0x1000) {
+ if ((uint64_t)new_off > h->u.file.inode.i_size)
+ new_off = h->u.file.inode.i_size;
+ }
h->u.file.offset = (uint32_t)new_off;
#if EXT2_READAHEAD_ENABLE
/* Invalidate cache on seek */
#ifdef LENIX_DEBUG
puts("[ext2] main() started");
#endif
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = ext2_sig_handler;
+ sigaction(SIGSEGV, &sa, NULL);
+ sigaction(SIGBUS, &sa, NULL);
ext2_bootstrap_portal = portal_get_bootstrap();
#ifdef LENIX_DEBUG
puts("[ext2] got bootstrap portal");
continue;
if ((size_t)got < sizeof(req))
memset(((uint8_t *)&req) + got, 0, sizeof(req) - (size_t)got);
+ ext2_log_request(&req);
fs_dispatch(&req);
#ifdef LENIX_BOOT_PROFILE
request_count++;
}
return 0;
}
+
+static void
+ext2_log_request(const struct ipc_fs_request *req)
+{
+#ifdef LENIX_DEBUG
+ static int log_budget = 64; /* avoid flooding console */
+
+ if (req == NULL || log_budget == 0)
+ return;
+ log_budget--;
+
+ switch (req->opcode) {
+ case IPC_FS_REQ_OPEN:
+ printf("[ext2][req] OPEN len=%u flags=0x%x\n",
+ req->body.open.path_len, req->body.open.flags);
+ break;
+ case IPC_FS_REQ_READ:
+ printf("[ext2][req] READ fd=%d len=%u\n",
+ req->body.read.fd, req->body.read.length);
+ break;
+ case IPC_FS_REQ_SEEK:
+ printf("[ext2][req] SEEK fd=%d off=%lld whence=%d\n",
+ req->body.seek.fd,
+ (long long)req->body.seek.offset,
+ req->body.seek.whence);
+ break;
+ case IPC_FS_REQ_STAT:
+ printf("[ext2][req] STAT len=%u\n", req->body.stat.path_len);
+ break;
+ default:
+ printf("[ext2][req] opcode=%u\n", req->opcode);
+ break;
+ }
+#else
+ (void)req;
+#endif
+}
+
+static void
+ext2_sig_handler(int sig)
+{
+ const char msg[] = "[ext2] FATAL: received signal\n";
+ write(STDERR_FILENO, msg, sizeof(msg) - 1);
+ _exit(128 + sig);
+}
#include <lenix/dirent.h>
blob - 7df4d909c3c886b430584adb030621bdffc8bb35
blob + 14148c7225887861770d8f0479b579e062e3c919
--- servers/fs/vfs/main.c
+++ servers/fs/vfs/main.c
pos >= handle->cache_file_offset &&
pos < handle->cache_file_offset + (int64_t)handle->cache_valid) {
- size_t cache_off = (size_t)(pos - handle->cache_file_offset);
- size_t cache_avail = handle->cache_valid - cache_off;
- size_t take = (requested_len < cache_avail) ? requested_len : cache_avail;
+ /* Bounds check: ensure cache_valid is within reasonable limits */
+ if (handle->cache_valid > VFS_READAHEAD_SIZE) {
+ /* Corrupted cache state - invalidate and fall through to fetch */
+ handle->cache_valid = 0;
+ handle->cache_file_offset = -1;
+ } else {
+ size_t cache_off = (size_t)(pos - handle->cache_file_offset);
+ /* Additional bounds check: ensure cache_off is within cache_valid */
+ if (cache_off >= handle->cache_valid) {
+ /* Corrupted offset - invalidate cache */
+ handle->cache_valid = 0;
+ handle->cache_file_offset = -1;
+ } else {
+ size_t cache_avail = handle->cache_valid - cache_off;
+ size_t take = (requested_len < cache_avail) ? requested_len : cache_avail;
+ /* Final bounds check: ensure we don't read beyond cache buffer */
+ if (cache_off + take > VFS_READAHEAD_SIZE) {
+ take = VFS_READAHEAD_SIZE - cache_off;
+ }
- memcpy(resp.body.read.data, handle->cache + cache_off, take);
- copied = take;
- pos += take;
- requested_len -= take;
+ memcpy(resp.body.read.data, handle->cache + cache_off, take);
+ copied = take;
+ pos += take;
+ requested_len -= take;
+ }
+ }
}
/* Step 2: If still need data, fetch with read-ahead */
if (fetched == 0)
break; /* EOF */
+ /* Bounds check: ensure we don't overflow the cache buffer */
+ if (total_fetched + fetched > VFS_READAHEAD_SIZE) {
+ fetched = VFS_READAHEAD_SIZE - total_fetched;
+ if (fetched == 0)
+ break;
+ }
+
memcpy(handle->cache + total_fetched, fs_response_buffer.body.read.data, fetched);
total_fetched += fetched;
blob - f08a0046a594d0d2877f33ed4d471433a43441ee
blob + 07d25e2e992bfc5eb1bc2e24026cc215268c1fdc
--- servers/namesvc/main.c
+++ servers/namesvc/main.c
resp.body.reg.status = -ENFILE;
namesvc_log("[namesvc] staged register failed: no free slots");
} else {
- memset(entry, 0, sizeof(*entry));
- entry->in_use = true;
- entry->staged = true;
- entry->name_len = len;
- memcpy(entry->name, req->body.reg.name, len);
- if (len < IPC_NAMESVC_MAX_NAME)
- entry->name[len] = '\0';
- entry->portal = req->body.reg.portal;
- entry->rights = req->body.reg.rights;
- entry->flags = req->body.reg.flags;
- entry->owner = caller;
- resp.body.reg.status = 0;
- namesvc_log("[namesvc] staged registration accepted");
+ /* Validate allocated entry pointer is within registry array */
+ if ((uintptr_t)entry < (uintptr_t)registry ||
+ (uintptr_t)entry >= (uintptr_t)(registry + NAMESVC_MAX_ENTRIES)) {
+ printf("[namesvc] namesvc_alloc returned invalid pointer: %p\n", (void*)entry);
+ resp.body.reg.status = -ENFILE;
+ entry = NULL;
+ } else {
+ memset(entry, 0, sizeof(*entry));
+ entry->in_use = true;
+ entry->staged = true;
+ entry->name_len = len;
+ memcpy(entry->name, req->body.reg.name, len);
+ if (len < IPC_NAMESVC_MAX_NAME)
+ entry->name[len] = '\0';
+ entry->portal = req->body.reg.portal;
+ entry->rights = req->body.reg.rights;
+ entry->flags = req->body.reg.flags;
+ entry->owner = caller;
+ resp.body.reg.status = 0;
+ namesvc_log("[namesvc] staged registration accepted");
+ }
}
}
} else {
#endif
entry = namesvc_find(req->body.reg.name, len);
/* namesvc is single-threaded: no concurrent mutation of entries */
- if (entry != NULL && entry->owner != caller && !is_admin)
- resp.body.reg.status = -1;
+ if (entry != NULL) {
+ /* Validate entry pointer is within registry array */
+ if ((uintptr_t)entry < (uintptr_t)registry ||
+ (uintptr_t)entry >= (uintptr_t)(registry + NAMESVC_MAX_ENTRIES)) {
+ printf("[namesvc] namesvc_find returned invalid pointer: %p\n", (void*)entry);
+ resp.body.reg.status = -1;
+ entry = NULL;
+ } else if (entry->owner != caller && !is_admin) {
+ resp.body.reg.status = -1;
+ }
+ }
if (resp.body.reg.status == 0 && entry == NULL)
entry = namesvc_alloc();
+ if (entry != NULL) {
+ /* Validate allocated entry pointer is within registry array */
+ if ((uintptr_t)entry < (uintptr_t)registry ||
+ (uintptr_t)entry >= (uintptr_t)(registry + NAMESVC_MAX_ENTRIES)) {
+ printf("[namesvc] namesvc_alloc returned invalid pointer: %p\n", (void*)entry);
+ resp.body.reg.status = -2;
+ entry = NULL;
+ }
+ }
if (entry == NULL && resp.body.reg.status == 0) {
resp.body.reg.status = -2;
namesvc_log("[namesvc] register failed: no free slots");
- } else if (resp.body.reg.status == 0) {
+ } else if (resp.body.reg.status == 0 && entry != NULL) {
entry->in_use = true;
entry->staged = false;
entry->name_len = len;
blob - /dev/null
blob + 536e0b6c963606255f2f14870754e7759d997b01 (mode 755)
--- /dev/null
+++ run_legacy-old.sh
+#!/bin/sh
+qemu-system-x86_64 -smp 4 -m 8G -cdrom build/x86_64/lenix.iso -serial stdio -display none 2>&1 | tee boot-qemu-x86_64-legacy.log
blob - /dev/null
blob + 576623adcd1853a2643570ef992cd586ca4238bf (mode 755)
--- /dev/null
+++ run_legacy.sh
+#!/bin/sh
+qemu-system-x86_64 -enable-kvm -cpu host -smp 4 -m 8G -M q35 -cdrom build/x86_64/lenix.iso -serial stdio -display none 2>&1 | tee boot-qemu-x86_64-legacy.log
blob - 870adfc3a7deb8a9bdf9c0216e5018e36b36a69b
blob + 86d197e7e75232fd4f641f1c6cb85898451a3328
--- user/apps/sh/sh.c
+++ user/apps/sh/sh.c
int rc;
struct sigaction sa;
+ write(STDERR_FILENO, "[sh] main entry\n", 16);
shell_pgid = getpgrp();
+ write(STDERR_FILENO, "[sh] getpgrp done\n", 18);
if (shell_pgid <= 0) {
shell_pgid = getpid();
setpgrp(0, shell_pgid);
+ write(STDERR_FILENO, "[sh] setpgrp done\n", 18);
}
+ write(STDERR_FILENO, "[sh] calling tcsetpgrp\n", 23);
tcsetpgrp(STDIN_FILENO, shell_pgid);
+ write(STDERR_FILENO, "[sh] tcsetpgrp done\n", 20);
signal(SIGTTOU, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
signal(SIGTTIN, SIG_IGN);
+ write(STDERR_FILENO, "[sh] signals configured\n", 24);
memset(&sa, 0, sizeof(sa));
sa.sa_handler = on_sigchld;
sigemptyset_local(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGCHLD, &sa, NULL);
+ write(STDERR_FILENO, "[sh] entering main loop\n", 24);
for (;;) {
#ifdef LENIX_DEBUG
blob - 20d17edffeea8e9634cab915365bf6560b49ca58
blob + 0417528615454f218fb8288ec5d5c8317b0f2d3f
--- user/init/main.c
+++ user/init/main.c
}
init_log_time("mounted / via ext2");
+ #if 0 /* Disabled: handoff not currently used (services array is empty) */
/* Stage and commit rootfs-backed services (initrd -> rootfs fast-path) */
#ifdef LENIX_DEBUG
puts("[init] starting initrd -> rootfs handoff");
} else {
puts("[init] handoff complete");
}
+ #endif
/* TODO: Mount /tmp as separate ext2 partition when disk partitioning is implemented.
* For now, /tmp is mounted as part of the root filesystem (read-only state).
char *argv[] = { "sh", NULL };
char *envp[] = { NULL };
pid_t pid;
+ write(STDERR_FILENO, "[init] launching /bin/sh\n", 25);
+ puts("[init] launching /bin/sh");
init_log_time("starting shell (/bin/sh)");
if (posix_spawnp(&pid, "sh", NULL, NULL, argv, envp) != 0) {
+ write(STDERR_FILENO, "[init] posix_spawnp failed\n", 27);
puts("[init] failed to start /bin/sh");
} else {
#ifdef LENIX_DEBUG
printf("[init] shell spawned pid=%d\n", (int)pid);
#endif
+ write(STDERR_FILENO, "[init] spawn OK\n", 16);
+ puts("[init] /bin/sh spawn succeeded");
init_log_time("shell spawned");
/* Reassert the console PTY as active after spawn in case activation
* was lost during portal handoff. This keeps shell input routed. */
else
puts("[init] warning: cannot activate console PTY (id=0)");
}
+
+ /*
+ * Keep init alive as a simple reaper/console anchor so the
+ * shell retains keyboard input and stdio portals are not
+ * revoked when init exits. Loop forever reaping any child.
+ */
+ for (;;) {
+ int status = 0;
+ pid_t reap = waitpid(-1, &status, 0);
+ if (reap <= 0) {
+ poll(NULL, 0, 100);
+ continue;
+ }
+ }
}
init_log_time("done; returning to scheduler");
blob - a2208b0f2956e3127965af89f2ef5b40e9500e85
blob + 3d7fd8f3cc1b0970f32785dcf9a571c9c6f9b429
--- user/runtime/include/lenix/block_service.h
+++ user/runtime/include/lenix/block_service.h
uint32_t blocks;
uint64_t lba;
uint32_t data_len;
+ uint32_t reserved; /* padding to distinguish from backend response (4124 bytes) */
uint8_t data[IPC_BLOCK_SERVICE_MAX_DATA];
};
blob - b2f2c7442e934f2a1690171a18009a332b313cd0
blob + 3954e6a0e171ee3c34730af7c20dfdca9189a6f8
--- user/runtime/src/syscall.c
+++ user/runtime/src/syscall.c
int
pty_alloc(struct pty_alloc_desc *desc)
{
- if (desc == NULL)
- return -1;
- long ret = __syscall6(SYS_pty_alloc, (long)desc, 0, 0, 0, 0, 0);
- if (ret < 0) {
- errno = (int)(-ret);
- return -1;
- }
- return (int)ret;
+ if (desc == NULL)
+ return -1;
+ long ret = __syscall6(SYS_pty_alloc, (long)desc, 0, 0, 0, 0, 0);
+ #ifdef LENIX_DEBUG
+ /* Debug: trace syscall return value */
+ {
+ char buf[64];
+ int i = 0;
+ const char *prefix = "[pty_alloc] ret=";
+ while (prefix[i]) buf[i] = prefix[i], i++;
+ if (ret == 0) {
+ buf[i++] = '0';
+ } else if (ret < 0) {
+ buf[i++] = '-';
+ long abs = -ret;
+ char tmp[20]; int t = 0;
+ do { tmp[t++] = '0' + (abs % 10); abs /= 10; } while (abs > 0);
+ while (t > 0) buf[i++] = tmp[--t];
+ } else {
+ char tmp[20]; int t = 0;
+ long v = ret;
+ while (v > 0) { tmp[t++] = '0' + (v % 10); v /= 10; }
+ while (t > 0) buf[i++] = tmp[--t];
+ }
+ buf[i++] = '\n'; buf[i] = 0;
+ __syscall6(SYS_write, 1, (long)buf, i, 0, 0, 0);
+ }
+ #endif
+ if (ret > 0) {
+ /* pty_alloc should never return a positive value; treat as success for now. */
+ ret = 0;
+ }
+ if (ret < 0) {
+ errno = (int)(-ret);
+ return -1;
+ }
+ return (int)ret;
}
int
int
fs_register_service(portal_handle_t bootstrap_handle)
{
- return (int)__syscall6(SYS_fs_register, bootstrap_handle, 0, 0, 0, 0, 0);
+ long ret = __syscall6(SYS_fs_register, bootstrap_handle, 0, 0, 0, 0, 0);
+
+ /* Unexpected positive values should be treated as success */
+ if (ret > 0)
+ ret = 0;
+ if (ret < 0) {
+ errno = (int)(-ret);
+ return -1;
+ }
+ return (int)ret;
}
int
{
if (buf == NULL || blocks == 0)
return -1;
+
+ /* Validate buffer pointer is in reasonable user-space range */
+ uintptr_t buf_addr = (uintptr_t)buf;
+ if (buf_addr < 0x1000 || buf_addr > 0x7fffffffffffULL) {
+ /* Invalid buffer address - likely corrupted pointer */
+ return -1;
+ }
+
uint8_t *base = (uint8_t *)buf;
size_t total = 0;
const uint32_t max_blocks_per_req = IPC_BLOCK_SERVICE_MAX_DATA / 512U;
if (batch > max_blocks_per_req)
batch = max_blocks_per_req;
- size_t bytes = (size_t)batch * 512U;
+ /* Validate base pointer before each operation */
+ uintptr_t base_addr = (uintptr_t)base;
+ if (base_addr < 0x1000 || base_addr > 0x7fffffffffffULL) {
+ /* Invalid base address - likely buffer overflow */
+ return -1;
+ }
+
ssize_t rc = block_service_rw(device, lba, base, batch, flags);
if (rc < 0)
return rc;
+ if (rc == 0) {
+ /* Short transfer - return error to avoid advancing to invalid address */
+ return -1;
+ }
- /* block_service_rw returns bytes transferred; ensure it matches expectation */
+ /* block_service_rw returns bytes transferred; advance by actual bytes, not expected */
total += (size_t)rc;
- base += bytes;
- lba += batch;
- remaining -= batch;
+ base += (size_t)rc; /* Advance by actual bytes transferred */
+ uint32_t sectors_transferred = (uint32_t)(rc / 512U);
+ if (sectors_transferred == 0 || (rc % 512U) != 0) {
+ /* Unaligned or zero transfer - error */
+ return -1;
+ }
+ lba += sectors_transferred;
+ remaining -= sectors_transferred;
}
return (ssize_t)total;
}
{
if (buf == NULL || blocks == 0)
return -1;
- return __syscall6(SYS_blocksvc_request, device, lba, (long)buf,
- blocks, flags, 0);
+
+ /* Retry a few times if the daemon returns a short transfer. */
+ const uint32_t max_attempts = 3;
+ for (uint32_t attempt = 0; attempt < max_attempts; attempt++) {
+ long ret = __syscall6(SYS_blocksvc_request, device, lba, (long)buf,
+ blocks, flags, 0);
+ if (ret == (long)((uint64_t)blocks * 512ULL))
+ return ret; /* full success */
+ if (ret < 0)
+ return ret; /* hard error */
+ /* Short transfer: try again after a brief pause */
+ struct pollfd pfd = { 0 };
+ poll(&pfd, 0, 1);
+ }
+ return -1; /* give up after retries */
}
int