summaryrefslogtreecommitdiffstats
path: root/rsnix/src/sched.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-01 19:34:40 +0200
committersalmonik <salmonik@tuta.io>2026-08-01 19:34:40 +0200
commit9b9758e5ee2564b04685eadf2b9ce23dd1060b86 (patch)
tree57fce9e715510ec70df70dc399b4440f3480fd77 /rsnix/src/sched.rs
parentfix pi-lens warnings: unwraps, shell SC2046, dup code (fmtbuf/scripts/hexdump... (diff)
downloadrs-dos-9b9758e5ee2564b04685eadf2b9ce23dd1060b86.tar.gz
rs-dos-9b9758e5ee2564b04685eadf2b9ce23dd1060b86.zip
rsnix v0.4.0: GUI input foundation — PS/2 mouse + scheduler message queues
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 <id>` routes real IRQ12 events into a task's queue, jobs shows q=. `make test-msg` + `make test-disk` regression pass.
Diffstat (limited to '')
-rw-r--r--rsnix/src/sched.rs199
1 files changed, 195 insertions, 4 deletions
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
42const NAME_MAX: usize = 24; 42const NAME_MAX: usize = 24;
43const PANIC_MAX: usize = 64; 43const PANIC_MAX: usize = 64;
44 44
45// ---------------------------------------------------------------------------
46// Inter-task message queues
47// ---------------------------------------------------------------------------
48
49/// Capacity of each task's message ring (power of two; full sends drop
50/// with a "queue full" error). Sized so input bursts (a 100 Hz mouse,
51/// key repeat) far outrun a blocked consumer: 16 × ~1.6 s at worst.
52const MSG_CAP: usize = 16;
53
54/// One inter-task message. Input events use the same decoded types the
55/// PS/2 drivers produce, so a window's input routing is "post what the
56/// driver would have delivered to the console". `User(u64)` is the
57/// generic escape hatch (window commands, app signals, …).
58#[derive(Clone, Copy, Debug)]
59pub enum TaskMsg {
60 Key(crate::ps2::KeyEvent),
61 Mouse(crate::ps2::MouseEvent),
62 Timer,
63 User(u64),
64}
65
66/// Decode a message for logging/display (no Debug dep in the shell task).
67pub fn msg_describe(msg: &TaskMsg) -> alloc::string::String {
68 use crate::ps2::KeyEvent;
69 match msg {
70 TaskMsg::Key(k) => match k {
71 KeyEvent::Char(c) => alloc::format!("Key('{}')", c),
72 KeyEvent::Enter => "Key(Enter)".into(),
73 KeyEvent::Backspace => "Key(Backspace)".into(),
74 KeyEvent::Tab => "Key(Tab)".into(),
75 KeyEvent::Escape => "Key(Escape)".into(),
76 KeyEvent::Ctrl(c) => alloc::format!("Key(Ctrl-{})", c),
77 KeyEvent::CtrlArrow(d) => alloc::format!("Key(CtrlArrow{})", d),
78 KeyEvent::ArrowUp => "Key(Up)".into(),
79 KeyEvent::ArrowDown => "Key(Down)".into(),
80 KeyEvent::ArrowLeft => "Key(Left)".into(),
81 KeyEvent::ArrowRight => "Key(Right)".into(),
82 KeyEvent::Home => "Key(Home)".into(),
83 KeyEvent::End => "Key(End)".into(),
84 KeyEvent::PageUp => "Key(PgUp)".into(),
85 KeyEvent::PageDown => "Key(PgDn)".into(),
86 KeyEvent::Delete => "Key(Delete)".into(),
87 KeyEvent::F(n) => alloc::format!("Key(F{})", n),
88 KeyEvent::Unknown(b) => alloc::format!("Key(0x{:02x})", b),
89 },
90 TaskMsg::Mouse(m) => alloc::format!(
91 "Mouse({:+},{:+} {} {})",
92 m.dx,
93 m.dy,
94 if m.left { "L" } else { "-" },
95 if m.right { "R" } else { "-" }
96 ),
97 TaskMsg::Timer => "Timer".into(),
98 TaskMsg::User(n) => alloc::format!("User({})", n),
99 }
100}
101
45#[derive(Clone, Copy, PartialEq, Eq, Debug)] 102#[derive(Clone, Copy, PartialEq, Eq, Debug)]
46pub enum TaskState { 103pub enum TaskState {
47 Ready, 104 Ready,
@@ -65,6 +122,13 @@ struct Task {
65 ticks: u64, 122 ticks: u64,
66 panic: [u8; PANIC_MAX], 123 panic: [u8; PANIC_MAX],
67 panic_len: u8, 124 panic_len: u8,
125 /// Per-task message ring (see `TaskMsg`). Accessed by the owning
126 /// task (pop) and by any poster (push) — both under the scheduler
127 /// lock, so `send` is callable from ISR or task context.
128 msg: [TaskMsg; MSG_CAP],
129 msg_head: u8,
130 msg_tail: u8,
131 msg_count: u8,
68} 132}
69 133
70const TASK_NONE: Task = Task { 134const TASK_NONE: Task = Task {
@@ -78,6 +142,10 @@ const TASK_NONE: Task = Task {
78 ticks: 0, 142 ticks: 0,
79 panic: [0; PANIC_MAX], 143 panic: [0; PANIC_MAX],
80 panic_len: 0, 144 panic_len: 0,
145 msg: [TaskMsg::User(0); MSG_CAP],
146 msg_head: 0,
147 msg_tail: 0,
148 msg_count: 0,
81}; 149};
82 150
83static mut TASKS: [Task; MAX_TASKS] = [TASK_NONE; MAX_TASKS]; 151static mut TASKS: [Task; MAX_TASKS] = [TASK_NONE; MAX_TASKS];
@@ -133,8 +201,18 @@ pub fn on_tick(cur: usize) -> usize {
133 TASKS[CURRENT].ticks += 1; 201 TASKS[CURRENT].ticks += 1;
134 let now = crate::interrupts::uptime_ms(); 202 let now = crate::interrupts::uptime_ms();
135 for t in 0..MAX_TASKS { 203 for t in 0..MAX_TASKS {
136 if TASKS[t].state == TaskState::Sleeping && TASKS[t].wake_at <= now { 204 match TASKS[t].state {
137 TASKS[t].state = TaskState::Ready; 205 TaskState::Sleeping if TASKS[t].wake_at <= now => {
206 TASKS[t].state = TaskState::Ready;
207 }
208 // Safety net for the recv race: a task that blocked
209 // *after* its message was queued (or whose poster never
210 // got to wake it) wakes on the next tick as long as its
211 // queue is non-empty.
212 TaskState::Blocked if TASKS[t].msg_count > 0 => {
213 TASKS[t].state = TaskState::Ready;
214 }
215 _ => {}
138 } 216 }
139 } 217 }
140 pick_next(cur) 218 pick_next(cur)
@@ -279,6 +357,10 @@ fn spawn_at(id: usize, entry: fn(), name: &str) -> bool {
279 ticks: 0, 357 ticks: 0,
280 panic: [0; PANIC_MAX], 358 panic: [0; PANIC_MAX],
281 panic_len: 0, 359 panic_len: 0,
360 msg: [TaskMsg::User(0); MSG_CAP],
361 msg_head: 0,
362 msg_tail: 0,
363 msg_count: 0,
282 }; 364 };
283 } 365 }
284 true 366 true
@@ -319,6 +401,110 @@ pub fn yield_now() {
319 yield_int(); 401 yield_int();
320} 402}
321 403
404// ---------------------------------------------------------------------------
405// Message passing
406// ---------------------------------------------------------------------------
407
408/// Post a message to a task's queue and wake it. Safe from any context:
409/// a task (e.g. a window manager routing input), the shell, or an ISR —
410/// the scheduler lock makes the queue mutation atomic w.r.t. switching.
411/// Returns an error when the task is gone or its queue is full.
412pub fn send(id: usize, msg: TaskMsg) -> Result<(), &'static str> {
413 if id >= MAX_TASKS {
414 return Err("no such task");
415 }
416 lock();
417 let r = unsafe {
418 let t = &TASKS[id];
419 if t.name_len == 0 || t.state == TaskState::Done {
420 Err("no such task")
421 } else if push_msg(id, msg) {
422 if TASKS[id].state == TaskState::Blocked {
423 TASKS[id].state = TaskState::Ready;
424 }
425 Ok(())
426 } else {
427 Err("queue full")
428 }
429 };
430 unlock();
431 r
432}
433
434/// Pop the oldest pending message of the current task, if any.
435pub fn recv() -> Option<TaskMsg> {
436 lock();
437 let m = unsafe { pop_msg(CURRENT) };
438 unlock();
439 m
440}
441
442/// Block until a message arrives, then return it. Wake is two-fold:
443/// `send` flips a Blocked task straight to Ready, and the tick scan
444/// (see `on_tick`) catches the race where the task blocked after the
445/// message was already queued.
446pub fn recv_blocking() -> TaskMsg {
447 loop {
448 if let Some(m) = recv() {
449 return m;
450 }
451 lock();
452 unsafe {
453 TASKS[CURRENT].state = TaskState::Blocked;
454 }
455 unlock();
456 yield_int();
457 }
458}
459
460/// Push onto the task's ring (caller holds the scheduler lock).
461unsafe fn push_msg(id: usize, msg: TaskMsg) -> bool {
462 unsafe {
463 let t = &mut TASKS[id];
464 if t.msg_count as usize >= MSG_CAP {
465 return false;
466 }
467 t.msg[t.msg_tail as usize] = msg;
468 t.msg_tail = (t.msg_tail + 1) & (MSG_CAP as u8 - 1);
469 t.msg_count += 1;
470 true
471 }
472}
473
474/// Pop from the current task's ring (caller holds the scheduler lock).
475unsafe fn pop_msg(id: usize) -> Option<TaskMsg> {
476 unsafe {
477 let t = &mut TASKS[id];
478 if t.msg_count == 0 {
479 return None;
480 }
481 let m = t.msg[t.msg_head as usize];
482 t.msg_head = (t.msg_head + 1) & (MSG_CAP as u8 - 1);
483 t.msg_count -= 1;
484 Some(m)
485 }
486}
487
488/// Pending message count of a task (for `jobs`).
489pub fn pending_msgs(id: usize) -> usize {
490 lock();
491 let n = unsafe { TASKS[id].msg_count as usize };
492 unlock();
493 n
494}
495
496/// Is the task slot live (spawned and not Done)? Used by the shell's
497/// `mouse <id>` routing check and by the window manager later.
498pub fn is_alive(id: usize) -> bool {
499 if id >= MAX_TASKS {
500 return false;
501 }
502 lock();
503 let alive = unsafe { TASKS[id].name_len != 0 && TASKS[id].state != TaskState::Done };
504 unlock();
505 alive
506}
507
322/// Software-interrupt the scheduler (vector 0x60). Net stack usage is 508/// Software-interrupt the scheduler (vector 0x60). Net stack usage is
323/// zero (the frame is popped by iretq), so no `nostack` — the compiler 509/// zero (the frame is popped by iretq), so no `nostack` — the compiler
324/// must not use the red zone around the interrupt. 510/// must not use the red zone around the interrupt.
@@ -418,11 +604,12 @@ pub fn jobs() -> Vec<String> {
418 } 604 }
419 }; 605 };
420 let mut line = alloc::format!( 606 let mut line = alloc::format!(
421 " {:>2} {:<12} {:<15} {:>6} ticks", 607 " {:>2} {:<12} {:<15} {:>6} ticks q={}",
422 i, 608 i,
423 name_str(i), 609 name_str(i),
424 state, 610 state,
425 t.ticks 611 t.ticks,
612 t.msg_count
426 ); 613 );
427 if t.panic_len > 0 { 614 if t.panic_len > 0 {
428 let m = core::str::from_utf8(&t.panic[..t.panic_len as usize]).unwrap_or("?"); 615 let m = core::str::from_utf8(&t.panic[..t.panic_len as usize]).unwrap_or("?");
@@ -505,6 +692,10 @@ pub fn init() {
505 ticks: 0, 692 ticks: 0,
506 panic: [0; PANIC_MAX], 693 panic: [0; PANIC_MAX],
507 panic_len: 0, 694 panic_len: 0,
695 msg: [TaskMsg::User(0); MSG_CAP],
696 msg_head: 0,
697 msg_tail: 0,
698 msg_count: 0,
508 }; 699 };
509 spawn_at(IDLE_TID, idle_main, "idle"); 700 spawn_at(IDLE_TID, idle_main, "idle");
510 SCHED_READY.store(true, Ordering::Relaxed); 701 SCHED_READY.store(true, Ordering::Relaxed);