From 9b9758e5ee2564b04685eadf2b9ce23dd1060b86 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 19:34:40 +0200 Subject: rsnix v0.4.0: GUI input foundation — PS/2 mouse + scheduler message queues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mouse (IRQ12, stream mode): ByteRing generalized for both PS/2 devices, ISR routes data-port bytes by the controller's aux bit; mouse_init with bounded-ACK handshake (boots without a mouse); packet decoder with bit-3 resync, screen-relative deltas, buttons. Exposed as /dev/mouse, the `mouse` stream command, and `make test-mouse`. Message queues (ADR 0003's GUI-era primitive): every task gets a static 16-slot TaskMsg ring; send/recv/recv_blocking with direct wake + tick scan safety net for the lost-wakeup race; `msgrecv`/`send` commands, `mouse ` routes real IRQ12 events into a task's queue, jobs shows q=. `make test-msg` + `make test-disk` regression pass. --- rsnix/src/fs.rs | 38 +++++++++ rsnix/src/interrupts.rs | 68 +++++++++++++---- rsnix/src/ps2.rs | 183 ++++++++++++++++++++++++++++++++++++++++++++ rsnix/src/sched.rs | 199 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 471 insertions(+), 17 deletions(-) (limited to 'rsnix/src') diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs index b560bfa..0b7a772 100644 --- a/rsnix/src/fs.rs +++ b/rsnix/src/fs.rs @@ -46,6 +46,7 @@ enum CharDevice { Zero, Console, Serial, + Mouse, } enum NodeData { @@ -171,6 +172,12 @@ pub fn init() -> (usize, usize) { FileType::Char, NodeData::Char(CharDevice::Serial), ); + add( + "mouse", + Some(dev), + FileType::Char, + NodeData::Char(CharDevice::Mouse), + ); add( "motd", @@ -700,11 +707,42 @@ fn dev_text(dev: CharDevice, _pos: u64) -> String { CharDevice::Version => version_text(), CharDevice::Meminfo => meminfo_text(), CharDevice::Klog => crate::klog::dump(), + CharDevice::Mouse => mouse_text(), CharDevice::Null | CharDevice::Console | CharDevice::Serial => String::new(), CharDevice::Zero => String::new(), } } +/// Render `/dev/mouse`: drain every pending PS/2 event (up to 32) into one +/// line each, so `cat /dev/mouse` shows the movement that happened since +/// the last read. Falls back to a clear message when there is no device or +/// nothing pending. +fn mouse_text() -> String { + if !crate::ps2::mouse_present() { + return String::from("mouse: no PS/2 mouse\n"); + } + let mut s = String::new(); + for _ in 0..32 { + let Some(ev) = crate::ps2::try_poll_mouse() else { + break; + }; + let mut b = String::new(); + b.push(if ev.left { 'L' } else { '-' }); + b.push(if ev.right { 'R' } else { '-' }); + b.push(if ev.middle { 'M' } else { '-' }); + s.push_str(&alloc::format!( + "dx={:+} dy={:+} buttons={}\n", + ev.dx, + ev.dy, + b + )); + } + if s.is_empty() { + s.push_str("mouse: no events\n"); + } + s +} + // --- self-test ------------------------------------------------------------ pub fn self_test() -> bool { diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs index 6abd54d..7ea0b07 100644 --- a/rsnix/src/interrupts.rs +++ b/rsnix/src/interrupts.rs @@ -153,19 +153,22 @@ pub fn uptime_ms() -> u64 { } // --------------------------------------------------------------------------- -// Lock-free SPSC ring buffer for keyboard scancodes +// Lock-free SPSC ring buffers for PS/2 input (keyboard scancodes + mouse +// bytes). One per device: the ISR routes each data-port byte to the right +// ring via the controller status register's aux bit (bit 5), so the two +// devices never steal each other's bytes even when both are pending at once. // --------------------------------------------------------------------------- /// Single-producer (ISR) / single-consumer (main) lock-free ring buffer. -struct KeyBuf { +struct ByteRing { buf: [core::cell::UnsafeCell; 256], head: AtomicU16, // next slot to read (written by consumer / main) tail: AtomicU16, // next slot to write (written by producer / ISR) } -unsafe impl Sync for KeyBuf {} +unsafe impl Sync for ByteRing {} -impl KeyBuf { +impl ByteRing { const fn new() -> Self { // Inline-const array repeat (stable since 1.79): each element is a // fresh `UnsafeCell::new(0)` value, so no interior-mutable const. @@ -205,7 +208,8 @@ impl KeyBuf { } } -static KEYBUF: KeyBuf = KeyBuf::new(); +static KEYBUF: ByteRing = ByteRing::new(); +static MOUSEBUF: ByteRing = ByteRing::new(); // --------------------------------------------------------------------------- // Interrupt stubs (assembly) and dispatch @@ -240,6 +244,14 @@ core::arch::global_asm!( " push 33", " jmp isr_common", "", + // PS/2 mouse IRQ (IRQ12 → vector 44). + ".globl isr_stub_44", + ".p2align 4", + "isr_stub_44:", + " push 0", + " push 44", + " jmp isr_common", + "", // Software yield (vector 0x60 — outside the PIC's 32-47 range, which // is why 0x40 was rejected: 0x40 = 64 decimal = slave IRQ 8 slot). ".globl isr_stub_60", @@ -345,13 +357,27 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize { eoi(0); crate::sched::on_tick(fp) } - 33 => { - let byte: u8; - core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16, + 33 | 44 => { + // PS/2 data byte. The controller's status register tells us + // which device it came from (bit 5 = auxiliary/mouse), so the + // byte is routed to the matching ring — both handlers must + // check, because when both devices have data pending the + // first IRQ serviced can find the other device's byte in the + // output buffer. + let status: u8; + core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16, options(nostack, preserves_flags)); - // Push scancode to ring buffer (called with interrupts disabled). - KEYBUF.push(byte); - eoi(1); + if status & 0x01 != 0 { + let byte: u8; + core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16, + options(nostack, preserves_flags)); + if status & 0x20 != 0 { + MOUSEBUF.push(byte); + } else { + KEYBUF.push(byte); + } + } + eoi((frame.vector - 32) as u8); fp } 8 => { @@ -381,6 +407,12 @@ pub fn read_scancode() -> Option { KEYBUF.pop() } +/// Read one mouse data byte from the interrupt-driven ring buffer +/// (non-blocking). Packets are assembled in `ps2::try_poll_mouse`. +pub fn read_mouse_byte() -> Option { + MOUSEBUF.pop() +} + /// Global tick counter (incremented by timer IRQ, ~100 Hz). u64 so /// uptime never wraps; `uptime_ms` interpolates the PIT counter for the /// sub-tick fraction. @@ -406,6 +438,8 @@ pub fn init() { // Override vectors for interrupt we actually handle. idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8, 0); idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8, 0); + // PS/2 mouse (IRQ12). + idt_set_entry(idt_ptr, 44, isr_stub_44 as *const () as u64, cs, 0x0Eu8, 0); // Yield (software interrupt). idt_set_entry( idt_ptr, @@ -439,13 +473,20 @@ pub fn init() { pit_set_rate(); PIT_READY.store(true, Ordering::Relaxed); - // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master. + // Unmask timer (IRQ0), keyboard (IRQ1) and the cascade line (IRQ2) + // on the master; unmask IRQ12 (PS/2 mouse) on the slave. let mut mask: u8; asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA, options(nostack, preserves_flags)); - mask &= !0x03u8; + mask &= !0x07u8; asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask, options(nostack, preserves_flags)); + let mut mask2: u8; + asm!("in al, dx", out("al") mask2, in("dx") PIC2_DATA, + options(nostack, preserves_flags)); + mask2 &= !0x10u8; // IRQ12 → bit 4 of the slave PIC + asm!("out dx, al", in("dx") PIC2_DATA, in("al") mask2, + options(nostack, preserves_flags)); // Enable interrupts. asm!("sti", options(nostack)); @@ -469,6 +510,7 @@ unsafe extern "C" { fn isr_stub_14(); fn isr_stub_32(); fn isr_stub_33(); + fn isr_stub_44(); fn isr_stub_60(); fn isr_stub_spurious(); } diff --git a/rsnix/src/ps2.rs b/rsnix/src/ps2.rs index 35848b6..5f4a804 100644 --- a/rsnix/src/ps2.rs +++ b/rsnix/src/ps2.rs @@ -16,6 +16,7 @@ const MOD_RALT: u8 = 0x20; const MOD_CAPS: u8 = 0x40; /// A decoded key event. +#[derive(Clone, Copy, Debug)] pub enum KeyEvent { Char(char), Enter, @@ -400,3 +401,185 @@ fn ctrl_held() -> bool { let state = kbd(); state.modifiers & (MOD_LCTRL | MOD_RCTRL) != 0 } + +// --------------------------------------------------------------------------- +// PS/2 mouse — auxiliary channel, IRQ12, stream mode (3-byte packets) +// --------------------------------------------------------------------------- + +// PS/2 controller commands (written to port 0x64). +const CMD_AUX_ENABLE: u8 = 0xA8; // enable the auxiliary (second) port +const CMD_READ_CFG: u8 = 0x20; // read Configuration Byte → port 0x60 +const CMD_WRITE_CFG: u8 = 0x60; // write Configuration Byte ← port 0x60 +const CMD_SEND_TO_AUX: u8 = 0xD4; // next byte on 0x60 goes to the mouse + +// Configuration Byte bits (as sent via CMD_READ_CFG / CMD_WRITE_CFG). +// Bit 0 (IRQ1/keyboard) is set by `init`; this driver preserves it by +// reading the byte and only ORing in the two mouse bits below. +const CFG_AUX_IRQ: u8 = 0x02; // bit 1 — IRQ12 (mouse) +const CFG_AUX_CLOCK: u8 = 0x20; // bit 5 — auxiliary port clock enable + +// Mouse device commands (sent via CMD_SEND_TO_AUX). +const AUX_SET_DEFAULTS: u8 = 0xF6; // 100 Hz sample, 4 counts/mm, 1:1 +const AUX_ENABLE: u8 = 0xF4; // start data reporting (stream mode) +const AUX_ACK: u8 = 0xFA; + +/// A decoded PS/2 mouse event (one 3-byte packet). +/// +/// Movement is **screen-relative**: `dx` positive = right, `dy` positive = +/// down. (The PS/2 wire format is Y-up-positive; the driver negates it so +/// consumers can apply the deltas to framebuffer coordinates directly.) +#[derive(Clone, Copy, Debug)] +pub struct MouseEvent { + pub dx: i16, + pub dy: i16, + pub left: bool, + pub right: bool, + pub middle: bool, +} + +/// Packet assembly state — lives in main context (consumer side), fed by +/// the raw byte ring that the IRQ12 handler fills. +struct MouseState { + phase: u8, // 0 = awaiting start byte, 1 = await X, 2 = await Y + pkt: [u8; 3], +} + +static mut MOUSE: MouseState = MouseState { + phase: 0, + pkt: [0; 3], +}; +static MOUSE_PRESENT: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false); + +/// True once `mouse_init` found a device that ACKed its setup commands. +pub fn mouse_present() -> bool { + MOUSE_PRESENT.load(core::sync::atomic::Ordering::Relaxed) +} + +/// Enable the auxiliary (mouse) channel and put the device into stream +/// mode. Returns false when no device ACKs (controller without a second +/// port, or a missing mouse) — the kernel then just runs keyboard-only. +/// +/// Must run *before* `interrupts::init` unmasks IRQ12: the init handshake +/// reads the data port directly, so any stray mouse bytes would otherwise +/// be split across the ISR path mid-handshake. +pub fn mouse_init() -> bool { + drain_input(); + unsafe { + // 1. Enable the auxiliary port itself. + wait_write(); + core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_AUX_ENABLE, + options(nostack, preserves_flags)); + + // 2. Configuration Byte: enable IRQ12 + the aux port clock. Read, + // OR the two bits, write back (preserves the keyboard IRQ bit + // that ps2::init set). + wait_write(); + core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_READ_CFG, + options(nostack, preserves_flags)); + wait_read(); + let mut cfg: u8; + core::arch::asm!("in al, dx", out("al") cfg, in("dx") DATA_PORT, + options(nostack, preserves_flags)); + cfg |= CFG_AUX_IRQ | CFG_AUX_CLOCK; + wait_write(); + core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_WRITE_CFG, + options(nostack, preserves_flags)); + wait_write(); + core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cfg, + options(nostack, preserves_flags)); + + // 3. Device setup. `set defaults` normalises the sample rate / + // resolution before enabling reporting, so the packet stream + // starts at a known cadence. Both commands need a device ACK; + // a timeout here means no mouse — bail out cleanly. + if !aux_command(AUX_SET_DEFAULTS) || !aux_command(AUX_ENABLE) { + return false; + } + } + MOUSE_PRESENT.store(true, core::sync::atomic::Ordering::Relaxed); + true +} + +/// Send one byte to the mouse via the 0xD4 channel and wait for its ACK. +/// The wait is bounded so a missing device cannot hang the boot. +fn aux_command(cmd: u8) -> bool { + unsafe { + wait_write(); + core::arch::asm!("out dx, al", in("dx") STATUS_PORT, in("al") CMD_SEND_TO_AUX, + options(nostack, preserves_flags)); + wait_write(); + core::arch::asm!("out dx, al", in("dx") DATA_PORT, in("al") cmd, + options(nostack, preserves_flags)); + for _ in 0..100_000 { + let status: u8; + core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT, + options(nostack, preserves_flags)); + if status & STAT_OUTPUT_FULL != 0 { + let b: u8; + core::arch::asm!("in al, dx", out("al") b, in("dx") DATA_PORT, + options(nostack, preserves_flags)); + return b == AUX_ACK; + } + } + } + false +} + +/// Non-blocking: decode the next pending mouse event, if a full packet has +/// arrived. Raw bytes are drained from the IRQ ring; incomplete packets +/// stay buffered in `MouseState` until the remaining bytes land. +pub fn try_poll_mouse() -> Option { + while let Some(b) = crate::interrupts::read_mouse_byte() { + if let Some(ev) = mouse_byte(b) { + return Some(ev); + } + } + None +} + +/// Feed one raw byte into the packet assembler. Byte 0 of every standard +/// PS/2 mouse packet has bit 3 set (the sync marker), so a lost byte (ring +/// overrun, init response) is detected on the next packet and the stream +/// re-syncs — the marker is only accepted as a packet start. +fn mouse_byte(b: u8) -> Option { + let m = unsafe { &mut *core::ptr::addr_of_mut!(MOUSE) }; + match m.phase { + 0 => { + if b & 0x08 != 0 { + m.pkt[0] = b; + m.phase = 1; + } + None + } + 1 => { + m.pkt[1] = b; + m.phase = 2; + None + } + _ => { + m.pkt[2] = b; + m.phase = 0; + Some(MouseEvent { + // 9-bit signed: low 8 bits in the data byte, sign bit in + // byte 0 (bit 4 = X, bit 5 = Y). + dx: signed9(m.pkt[0], m.pkt[1], 0x10), + // PS/2 reports Y positive = up; flip to screen convention. + dy: -signed9(m.pkt[0], m.pkt[2], 0x20), + left: m.pkt[0] & 0x01 != 0, + right: m.pkt[0] & 0x02 != 0, + middle: m.pkt[0] & 0x04 != 0, + }) + } + } +} + +/// Sign-extend an 8-bit movement byte to 9 bits using the packet's sign +/// flag (bit `sign` of byte 0), widening to i16 for consumers. +fn signed9(b0: u8, b: u8, sign: u8) -> i16 { + let mut v = (b as i8) as i16; + if b0 & sign != 0 { + // Set the 9th bit (0xFF00 = sign-extension mask for i16). + v |= 0xFF00u16 as i16; + } + v +} diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs index 6ffa745..dbe3647 100644 --- a/rsnix/src/sched.rs +++ b/rsnix/src/sched.rs @@ -42,6 +42,63 @@ const STACK_PAGES: usize = 3; // 1 guard page + 8 KiB stack const NAME_MAX: usize = 24; const PANIC_MAX: usize = 64; +// --------------------------------------------------------------------------- +// Inter-task message queues +// --------------------------------------------------------------------------- + +/// Capacity of each task's message ring (power of two; full sends drop +/// with a "queue full" error). Sized so input bursts (a 100 Hz mouse, +/// key repeat) far outrun a blocked consumer: 16 × ~1.6 s at worst. +const MSG_CAP: usize = 16; + +/// One inter-task message. Input events use the same decoded types the +/// PS/2 drivers produce, so a window's input routing is "post what the +/// driver would have delivered to the console". `User(u64)` is the +/// generic escape hatch (window commands, app signals, …). +#[derive(Clone, Copy, Debug)] +pub enum TaskMsg { + Key(crate::ps2::KeyEvent), + Mouse(crate::ps2::MouseEvent), + Timer, + User(u64), +} + +/// Decode a message for logging/display (no Debug dep in the shell task). +pub fn msg_describe(msg: &TaskMsg) -> alloc::string::String { + use crate::ps2::KeyEvent; + match msg { + TaskMsg::Key(k) => match k { + KeyEvent::Char(c) => alloc::format!("Key('{}')", c), + KeyEvent::Enter => "Key(Enter)".into(), + KeyEvent::Backspace => "Key(Backspace)".into(), + KeyEvent::Tab => "Key(Tab)".into(), + KeyEvent::Escape => "Key(Escape)".into(), + KeyEvent::Ctrl(c) => alloc::format!("Key(Ctrl-{})", c), + KeyEvent::CtrlArrow(d) => alloc::format!("Key(CtrlArrow{})", d), + KeyEvent::ArrowUp => "Key(Up)".into(), + KeyEvent::ArrowDown => "Key(Down)".into(), + KeyEvent::ArrowLeft => "Key(Left)".into(), + KeyEvent::ArrowRight => "Key(Right)".into(), + KeyEvent::Home => "Key(Home)".into(), + KeyEvent::End => "Key(End)".into(), + KeyEvent::PageUp => "Key(PgUp)".into(), + KeyEvent::PageDown => "Key(PgDn)".into(), + KeyEvent::Delete => "Key(Delete)".into(), + KeyEvent::F(n) => alloc::format!("Key(F{})", n), + KeyEvent::Unknown(b) => alloc::format!("Key(0x{:02x})", b), + }, + TaskMsg::Mouse(m) => alloc::format!( + "Mouse({:+},{:+} {} {})", + m.dx, + m.dy, + if m.left { "L" } else { "-" }, + if m.right { "R" } else { "-" } + ), + TaskMsg::Timer => "Timer".into(), + TaskMsg::User(n) => alloc::format!("User({})", n), + } +} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum TaskState { Ready, @@ -65,6 +122,13 @@ struct Task { ticks: u64, panic: [u8; PANIC_MAX], panic_len: u8, + /// Per-task message ring (see `TaskMsg`). Accessed by the owning + /// task (pop) and by any poster (push) — both under the scheduler + /// lock, so `send` is callable from ISR or task context. + msg: [TaskMsg; MSG_CAP], + msg_head: u8, + msg_tail: u8, + msg_count: u8, } const TASK_NONE: Task = Task { @@ -78,6 +142,10 @@ const TASK_NONE: Task = Task { ticks: 0, panic: [0; PANIC_MAX], panic_len: 0, + msg: [TaskMsg::User(0); MSG_CAP], + msg_head: 0, + msg_tail: 0, + msg_count: 0, }; static mut TASKS: [Task; MAX_TASKS] = [TASK_NONE; MAX_TASKS]; @@ -133,8 +201,18 @@ pub fn on_tick(cur: usize) -> usize { TASKS[CURRENT].ticks += 1; let now = crate::interrupts::uptime_ms(); for t in 0..MAX_TASKS { - if TASKS[t].state == TaskState::Sleeping && TASKS[t].wake_at <= now { - TASKS[t].state = TaskState::Ready; + match TASKS[t].state { + TaskState::Sleeping if TASKS[t].wake_at <= now => { + TASKS[t].state = TaskState::Ready; + } + // Safety net for the recv race: a task that blocked + // *after* its message was queued (or whose poster never + // got to wake it) wakes on the next tick as long as its + // queue is non-empty. + TaskState::Blocked if TASKS[t].msg_count > 0 => { + TASKS[t].state = TaskState::Ready; + } + _ => {} } } pick_next(cur) @@ -279,6 +357,10 @@ fn spawn_at(id: usize, entry: fn(), name: &str) -> bool { ticks: 0, panic: [0; PANIC_MAX], panic_len: 0, + msg: [TaskMsg::User(0); MSG_CAP], + msg_head: 0, + msg_tail: 0, + msg_count: 0, }; } true @@ -319,6 +401,110 @@ pub fn yield_now() { yield_int(); } +// --------------------------------------------------------------------------- +// Message passing +// --------------------------------------------------------------------------- + +/// Post a message to a task's queue and wake it. Safe from any context: +/// a task (e.g. a window manager routing input), the shell, or an ISR — +/// the scheduler lock makes the queue mutation atomic w.r.t. switching. +/// Returns an error when the task is gone or its queue is full. +pub fn send(id: usize, msg: TaskMsg) -> Result<(), &'static str> { + if id >= MAX_TASKS { + return Err("no such task"); + } + lock(); + let r = unsafe { + let t = &TASKS[id]; + if t.name_len == 0 || t.state == TaskState::Done { + Err("no such task") + } else if push_msg(id, msg) { + if TASKS[id].state == TaskState::Blocked { + TASKS[id].state = TaskState::Ready; + } + Ok(()) + } else { + Err("queue full") + } + }; + unlock(); + r +} + +/// Pop the oldest pending message of the current task, if any. +pub fn recv() -> Option { + lock(); + let m = unsafe { pop_msg(CURRENT) }; + unlock(); + m +} + +/// Block until a message arrives, then return it. Wake is two-fold: +/// `send` flips a Blocked task straight to Ready, and the tick scan +/// (see `on_tick`) catches the race where the task blocked after the +/// message was already queued. +pub fn recv_blocking() -> TaskMsg { + loop { + if let Some(m) = recv() { + return m; + } + lock(); + unsafe { + TASKS[CURRENT].state = TaskState::Blocked; + } + unlock(); + yield_int(); + } +} + +/// Push onto the task's ring (caller holds the scheduler lock). +unsafe fn push_msg(id: usize, msg: TaskMsg) -> bool { + unsafe { + let t = &mut TASKS[id]; + if t.msg_count as usize >= MSG_CAP { + return false; + } + t.msg[t.msg_tail as usize] = msg; + t.msg_tail = (t.msg_tail + 1) & (MSG_CAP as u8 - 1); + t.msg_count += 1; + true + } +} + +/// Pop from the current task's ring (caller holds the scheduler lock). +unsafe fn pop_msg(id: usize) -> Option { + unsafe { + let t = &mut TASKS[id]; + if t.msg_count == 0 { + return None; + } + let m = t.msg[t.msg_head as usize]; + t.msg_head = (t.msg_head + 1) & (MSG_CAP as u8 - 1); + t.msg_count -= 1; + Some(m) + } +} + +/// Pending message count of a task (for `jobs`). +pub fn pending_msgs(id: usize) -> usize { + lock(); + let n = unsafe { TASKS[id].msg_count as usize }; + unlock(); + n +} + +/// Is the task slot live (spawned and not Done)? Used by the shell's +/// `mouse ` routing check and by the window manager later. +pub fn is_alive(id: usize) -> bool { + if id >= MAX_TASKS { + return false; + } + lock(); + let alive = unsafe { TASKS[id].name_len != 0 && TASKS[id].state != TaskState::Done }; + unlock(); + alive +} + /// Software-interrupt the scheduler (vector 0x60). Net stack usage is /// zero (the frame is popped by iretq), so no `nostack` — the compiler /// must not use the red zone around the interrupt. @@ -418,11 +604,12 @@ pub fn jobs() -> Vec { } }; let mut line = alloc::format!( - " {:>2} {:<12} {:<15} {:>6} ticks", + " {:>2} {:<12} {:<15} {:>6} ticks q={}", i, name_str(i), state, - t.ticks + t.ticks, + t.msg_count ); if t.panic_len > 0 { let m = core::str::from_utf8(&t.panic[..t.panic_len as usize]).unwrap_or("?"); @@ -505,6 +692,10 @@ pub fn init() { ticks: 0, panic: [0; PANIC_MAX], panic_len: 0, + msg: [TaskMsg::User(0); MSG_CAP], + msg_head: 0, + msg_tail: 0, + msg_count: 0, }; spawn_at(IDLE_TID, idle_main, "idle"); SCHED_READY.store(true, Ordering::Relaxed); -- cgit v1.2.3