diff options
Diffstat (limited to 'rsnix/src/sched.rs')
| -rw-r--r-- | rsnix/src/sched.rs | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs new file mode 100644 index 0000000..4b10503 --- /dev/null +++ b/rsnix/src/sched.rs | |||
| @@ -0,0 +1,544 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // sched — preemptive round-robin kernel tasks. | ||
| 3 | // | ||
| 4 | // 16 static task slots. The 100 Hz PIT tick (vector 32) and the | ||
| 5 | // software yield vector (0x60) go through `isr_common`, which saves the | ||
| 6 | // running task's full IntFrame on its own stack and asks the scheduler | ||
| 7 | // for the next frame pointer — the asm then moves rsp there and iretq's, | ||
| 8 | // which *is* the context switch. | ||
| 9 | // | ||
| 10 | // - task 0: the shell (boot stack, never allocated by us) | ||
| 11 | // - task 1: idle (hlt loop, always ready — the lowest-priority | ||
| 12 | // fallback) | ||
| 13 | // - tasks 2..: spawn'd, 8 KiB stack + one unmapped guard page below | ||
| 14 | // | ||
| 15 | // Preemption is disabled while the scheduler lock (PREEMPT_DEPTH) is | ||
| 16 | // held: the tick/yield handlers bail out entirely when the depth is | ||
| 17 | // > 0, so table mutations and klog/serial line writes are atomic with | ||
| 18 | // respect to switching. The lock is a counter, not a mutex — re-entrant | ||
| 19 | // by design (a panic mid-emit increments again; the panic path clears | ||
| 20 | // it, because a dead task's lock is meaningless). | ||
| 21 | // | ||
| 22 | // Task page faults (vector 14) run on a dedicated IST stack (see gdt.rs) | ||
| 23 | // so a stack overflow inside a guard page kills the task gracefully | ||
| 24 | // instead of double-faulting the machine. | ||
| 25 | // --------------------------------------------------------------------------- | ||
| 26 | use core::arch::asm; | ||
| 27 | use core::fmt; | ||
| 28 | use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; | ||
| 29 | |||
| 30 | extern crate alloc; | ||
| 31 | use alloc::string::String; | ||
| 32 | use alloc::vec::Vec; | ||
| 33 | |||
| 34 | use crate::interrupts::IntFrame; | ||
| 35 | use crate::memory; | ||
| 36 | use crate::paging; | ||
| 37 | |||
| 38 | pub const MAX_TASKS: usize = 16; | ||
| 39 | pub const SHELL_TID: usize = 0; | ||
| 40 | pub const IDLE_TID: usize = 1; | ||
| 41 | const STACK_PAGES: usize = 3; // 1 guard page + 8 KiB stack | ||
| 42 | const NAME_MAX: usize = 24; | ||
| 43 | const PANIC_MAX: usize = 64; | ||
| 44 | |||
| 45 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] | ||
| 46 | pub enum TaskState { | ||
| 47 | Ready, | ||
| 48 | Sleeping, | ||
| 49 | Blocked, | ||
| 50 | Done, | ||
| 51 | } | ||
| 52 | |||
| 53 | struct Task { | ||
| 54 | name: [u8; NAME_MAX], | ||
| 55 | name_len: u8, | ||
| 56 | state: TaskState, | ||
| 57 | /// Frame pointer to restore on switch-in (set at spawn; stored on | ||
| 58 | /// every switch-away). | ||
| 59 | saved_rsp: usize, | ||
| 60 | /// Physical base of the stack allocation (0 = none, e.g. the shell). | ||
| 61 | stack: usize, | ||
| 62 | /// Guard page address (unmapped; 0 = none). | ||
| 63 | guard: usize, | ||
| 64 | wake_at: u64, | ||
| 65 | ticks: u64, | ||
| 66 | panic: [u8; PANIC_MAX], | ||
| 67 | panic_len: u8, | ||
| 68 | } | ||
| 69 | |||
| 70 | const TASK_NONE: Task = Task { | ||
| 71 | name: [0; NAME_MAX], | ||
| 72 | name_len: 0, | ||
| 73 | state: TaskState::Done, | ||
| 74 | saved_rsp: 0, | ||
| 75 | stack: 0, | ||
| 76 | guard: 0, | ||
| 77 | wake_at: 0, | ||
| 78 | ticks: 0, | ||
| 79 | panic: [0; PANIC_MAX], | ||
| 80 | panic_len: 0, | ||
| 81 | }; | ||
| 82 | |||
| 83 | static mut TASKS: [Task; MAX_TASKS] = [TASK_NONE; MAX_TASKS]; | ||
| 84 | static mut CURRENT: usize = SHELL_TID; | ||
| 85 | static mut KERNEL_CS: u16 = 0; | ||
| 86 | static mut KERNEL_SS: u16 = 0; | ||
| 87 | |||
| 88 | static SCHED_READY: AtomicBool = AtomicBool::new(false); | ||
| 89 | static IN_ISR: AtomicBool = AtomicBool::new(false); | ||
| 90 | static PREEMPT_DEPTH: AtomicU64 = AtomicU64::new(0); | ||
| 91 | |||
| 92 | // --------------------------------------------------------------------------- | ||
| 93 | // Preemption lock | ||
| 94 | // --------------------------------------------------------------------------- | ||
| 95 | |||
| 96 | /// Disable preemption (re-entrant counter). The tick/yield handlers | ||
| 97 | /// skip all scheduling work while the depth is > 0. | ||
| 98 | pub fn lock() { | ||
| 99 | PREEMPT_DEPTH.fetch_add(1, Ordering::Relaxed); | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Re-enable preemption (must pair with `lock`). | ||
| 103 | pub fn unlock() { | ||
| 104 | PREEMPT_DEPTH.fetch_sub(1, Ordering::Relaxed); | ||
| 105 | } | ||
| 106 | |||
| 107 | pub fn in_isr() -> bool { | ||
| 108 | IN_ISR.load(Ordering::Relaxed) | ||
| 109 | } | ||
| 110 | pub fn set_in_isr(v: bool) { | ||
| 111 | IN_ISR.store(v, Ordering::Relaxed); | ||
| 112 | } | ||
| 113 | pub fn is_ready() -> bool { | ||
| 114 | SCHED_READY.load(Ordering::Relaxed) | ||
| 115 | } | ||
| 116 | pub fn current() -> usize { | ||
| 117 | unsafe { CURRENT } | ||
| 118 | } | ||
| 119 | |||
| 120 | // --------------------------------------------------------------------------- | ||
| 121 | // Tick / yield entry points (called from the ISR dispatch) | ||
| 122 | // --------------------------------------------------------------------------- | ||
| 123 | |||
| 124 | /// Timer tick: wake sleepers, account ticks, pick the next task. | ||
| 125 | /// Receives the interrupted task's frame pointer; returns the frame | ||
| 126 | /// pointer the asm should switch to (the same one when no switch). | ||
| 127 | pub fn on_tick(cur: usize) -> usize { | ||
| 128 | if !is_ready() || PREEMPT_DEPTH.load(Ordering::Relaxed) > 0 { | ||
| 129 | return cur; | ||
| 130 | } | ||
| 131 | unsafe { | ||
| 132 | TASKS[CURRENT].saved_rsp = cur; | ||
| 133 | TASKS[CURRENT].ticks += 1; | ||
| 134 | let now = crate::interrupts::uptime_ms(); | ||
| 135 | for t in 0..MAX_TASKS { | ||
| 136 | if TASKS[t].state == TaskState::Sleeping && TASKS[t].wake_at <= now { | ||
| 137 | TASKS[t].state = TaskState::Ready; | ||
| 138 | } | ||
| 139 | } | ||
| 140 | pick_next(cur) | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | /// Software yield (vector 0x60). | ||
| 145 | pub fn on_yield(cur: usize) -> usize { | ||
| 146 | if !is_ready() || PREEMPT_DEPTH.load(Ordering::Relaxed) > 0 { | ||
| 147 | return cur; | ||
| 148 | } | ||
| 149 | unsafe { | ||
| 150 | TASKS[CURRENT].saved_rsp = cur; | ||
| 151 | pick_next(cur) | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | /// Round-robin over Ready tasks, skipping the idle task unless nothing | ||
| 156 | /// else can run. Returns the next frame pointer (or `cur` to stay). | ||
| 157 | unsafe fn pick_next(cur: usize) -> usize { | ||
| 158 | unsafe { | ||
| 159 | for i in 1..MAX_TASKS { | ||
| 160 | let idx = (CURRENT + i) % MAX_TASKS; | ||
| 161 | if idx == IDLE_TID { | ||
| 162 | continue; | ||
| 163 | } | ||
| 164 | if TASKS[idx].state == TaskState::Ready { | ||
| 165 | CURRENT = idx; | ||
| 166 | return TASKS[idx].saved_rsp; | ||
| 167 | } | ||
| 168 | } | ||
| 169 | if CURRENT != IDLE_TID && TASKS[IDLE_TID].state == TaskState::Ready { | ||
| 170 | CURRENT = IDLE_TID; | ||
| 171 | return TASKS[IDLE_TID].saved_rsp; | ||
| 172 | } | ||
| 173 | cur | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | // --------------------------------------------------------------------------- | ||
| 178 | // Spawn / lifecycle | ||
| 179 | // --------------------------------------------------------------------------- | ||
| 180 | |||
| 181 | /// Spawn a kernel task running `entry` on its own 8 KiB stack with a | ||
| 182 | /// guard page below it. Returns the task id, or None when the table is | ||
| 183 | /// full (Done slots are reaped first). `entry` runs inside a | ||
| 184 | /// trampoline, so returning from it exits the task cleanly. | ||
| 185 | pub fn spawn(entry: fn(), name: &str) -> Option<usize> { | ||
| 186 | lock(); | ||
| 187 | let id = unsafe { | ||
| 188 | // Reap a Done task with a stack, or take a never-used slot. | ||
| 189 | let mut slot = None; | ||
| 190 | for i in 2..MAX_TASKS { | ||
| 191 | if TASKS[i].state == TaskState::Done && TASKS[i].stack != 0 { | ||
| 192 | reap(i); | ||
| 193 | slot = Some(i); | ||
| 194 | break; | ||
| 195 | } | ||
| 196 | if TASKS[i].state == TaskState::Done && TASKS[i].stack == 0 && TASKS[i].name_len == 0 { | ||
| 197 | slot = Some(i); | ||
| 198 | break; | ||
| 199 | } | ||
| 200 | } | ||
| 201 | slot? | ||
| 202 | }; | ||
| 203 | let ok = spawn_at(id, entry, name); | ||
| 204 | unlock(); | ||
| 205 | if ok { | ||
| 206 | Some(id) | ||
| 207 | } else { | ||
| 208 | None | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | unsafe fn reap(id: usize) { | ||
| 213 | unsafe { | ||
| 214 | let t = &mut TASKS[id]; | ||
| 215 | if t.stack != 0 { | ||
| 216 | // Restore the guard page's mapping so the pages are reusable. | ||
| 217 | paging::map_page(t.stack, paging::PAGE_WRITABLE); | ||
| 218 | memory::free_pages(t.stack, STACK_PAGES); | ||
| 219 | } | ||
| 220 | *t = TASK_NONE; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | fn spawn_at(id: usize, entry: fn(), name: &str) -> bool { | ||
| 225 | let Some(base) = memory::alloc_pages(STACK_PAGES) else { | ||
| 226 | return false; | ||
| 227 | }; | ||
| 228 | // The first page is the guard — unmap it so any stack overflow | ||
| 229 | // faults there (caught on the vector-14 IST stack). | ||
| 230 | if !paging::unmap_page(base) { | ||
| 231 | memory::free_pages(base, STACK_PAGES); | ||
| 232 | return false; | ||
| 233 | } | ||
| 234 | let top = base + STACK_PAGES * memory::PAGE_SIZE; | ||
| 235 | let frame = (top - core::mem::size_of::<IntFrame>()) as *mut IntFrame; | ||
| 236 | let mut name_buf = [0u8; NAME_MAX]; | ||
| 237 | let n = name.len().min(NAME_MAX); | ||
| 238 | name_buf[..n].copy_from_slice(&name.as_bytes()[..n]); | ||
| 239 | unsafe { | ||
| 240 | core::ptr::write( | ||
| 241 | frame, | ||
| 242 | IntFrame { | ||
| 243 | r15: 0, | ||
| 244 | r14: 0, | ||
| 245 | r13: 0, | ||
| 246 | r12: 0, | ||
| 247 | r11: 0, | ||
| 248 | r10: 0, | ||
| 249 | r9: 0, | ||
| 250 | r8: 0, | ||
| 251 | rdi: 0, | ||
| 252 | rsi: 0, | ||
| 253 | rbp: 0, | ||
| 254 | rbx: 0, | ||
| 255 | rdx: 0, | ||
| 256 | rcx: entry as usize as u64, // MS x64 ABI: first arg | ||
| 257 | rax: 0, | ||
| 258 | vector: 0, | ||
| 259 | error: 0, | ||
| 260 | rip: trampoline as *const () as u64, | ||
| 261 | cs: KERNEL_CS as u64, | ||
| 262 | rflags: 0x202, // IF | reserved bit 1 | ||
| 263 | // QEMU's iretq always pops RSP/SS from the frame tail. | ||
| 264 | // rsp = the stack top so the first iretq lands with a | ||
| 265 | // valid stack pointer (the trampoline then descends from | ||
| 266 | // it normally); ss mirrors the kernel data selector. | ||
| 267 | rsp: top as u64, | ||
| 268 | ss: KERNEL_SS as u64, | ||
| 269 | }, | ||
| 270 | ); | ||
| 271 | TASKS[id] = Task { | ||
| 272 | name: name_buf, | ||
| 273 | name_len: n as u8, | ||
| 274 | state: TaskState::Ready, | ||
| 275 | saved_rsp: frame as usize, | ||
| 276 | stack: base, | ||
| 277 | guard: base, | ||
| 278 | wake_at: 0, | ||
| 279 | ticks: 0, | ||
| 280 | panic: [0; PANIC_MAX], | ||
| 281 | panic_len: 0, | ||
| 282 | }; | ||
| 283 | } | ||
| 284 | true | ||
| 285 | } | ||
| 286 | |||
| 287 | /// Entry trampoline: run the task body, then exit the task when it | ||
| 288 | /// returns. Runs with the synthetic frame's registers (rcx = entry). | ||
| 289 | unsafe extern "C" fn trampoline(entry: usize) -> ! { | ||
| 290 | let f: fn() = unsafe { core::mem::transmute(entry as *const ()) }; | ||
| 291 | f(); | ||
| 292 | task_exit() | ||
| 293 | } | ||
| 294 | |||
| 295 | fn task_exit() -> ! { | ||
| 296 | lock(); | ||
| 297 | unsafe { | ||
| 298 | TASKS[CURRENT].state = TaskState::Done; | ||
| 299 | } | ||
| 300 | unlock(); | ||
| 301 | yield_int(); | ||
| 302 | unreachable!() | ||
| 303 | } | ||
| 304 | |||
| 305 | /// Block the current task for `ms` milliseconds. The shell uses this | ||
| 306 | /// for `sleep`; background tasks keep running meanwhile. | ||
| 307 | pub fn sleep_ms(ms: u64) { | ||
| 308 | lock(); | ||
| 309 | unsafe { | ||
| 310 | TASKS[CURRENT].state = TaskState::Sleeping; | ||
| 311 | TASKS[CURRENT].wake_at = crate::interrupts::uptime_ms() + ms; | ||
| 312 | } | ||
| 313 | unlock(); | ||
| 314 | yield_int(); | ||
| 315 | } | ||
| 316 | |||
| 317 | /// Cooperatively yield the rest of the current quantum. | ||
| 318 | pub fn yield_now() { | ||
| 319 | yield_int(); | ||
| 320 | } | ||
| 321 | |||
| 322 | /// Software-interrupt the scheduler (vector 0x60). Net stack usage is | ||
| 323 | /// zero (the frame is popped by iretq), so no `nostack` — the compiler | ||
| 324 | /// must not use the red zone around the interrupt. | ||
| 325 | #[inline(never)] | ||
| 326 | fn yield_int() { | ||
| 327 | unsafe { | ||
| 328 | asm!("int 0x60"); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | |||
| 332 | // --------------------------------------------------------------------------- | ||
| 333 | // Task panics and faults | ||
| 334 | // --------------------------------------------------------------------------- | ||
| 335 | |||
| 336 | /// Kill the current task with a panic message: logs it, marks the task | ||
| 337 | /// Done, drops its scheduler lock (a dead task's lock is meaningless), | ||
| 338 | /// and switches away forever. Called by the panic hook (task context) | ||
| 339 | /// and by the page-fault handler (IST stack, ISR context). | ||
| 340 | pub fn abort_current(msg: &str) -> ! { | ||
| 341 | unsafe { | ||
| 342 | let id = CURRENT; | ||
| 343 | let n = msg.len().min(PANIC_MAX); | ||
| 344 | TASKS[id].panic[..n].copy_from_slice(&msg.as_bytes()[..n]); | ||
| 345 | TASKS[id].panic_len = n as u8; | ||
| 346 | TASKS[id].state = TaskState::Done; | ||
| 347 | crate::serial_println!( | ||
| 348 | "sched: task {} \"{}\" panicked: {} (rsp_saved={:#x} stack={:#x} guard={:#x})", | ||
| 349 | id, | ||
| 350 | name_str(id), | ||
| 351 | msg, | ||
| 352 | TASKS[id].saved_rsp, | ||
| 353 | TASKS[id].stack, | ||
| 354 | TASKS[id].guard | ||
| 355 | ); | ||
| 356 | PREEMPT_DEPTH.store(0, Ordering::Relaxed); | ||
| 357 | } | ||
| 358 | yield_int(); | ||
| 359 | unreachable!() | ||
| 360 | } | ||
| 361 | |||
| 362 | /// Vector 14 (page fault) handler — runs on the dedicated IST stack. | ||
| 363 | /// A fault in a real task (id >= 2) kills the task gracefully; anything | ||
| 364 | /// else (shell, idle, pre-scheduler) is a kernel bug: halt. | ||
| 365 | pub fn page_fault(cr2: u64, frame_ptr: usize) -> usize { | ||
| 366 | if !is_ready() || unsafe { CURRENT } <= IDLE_TID { | ||
| 367 | crate::serial_println!("pgfault: kernel page fault at {:#x} — halting", cr2); | ||
| 368 | crate::interrupts::halt(); | ||
| 369 | } | ||
| 370 | let g = unsafe { TASKS[CURRENT].guard }; | ||
| 371 | let in_guard = g != 0 && cr2 >= g as u64 && cr2 < (g + memory::PAGE_SIZE) as u64; | ||
| 372 | let rip = unsafe { (frame_ptr as *const IntFrame).read().rip }; | ||
| 373 | let mut buf = [0u8; 96]; | ||
| 374 | let n = { | ||
| 375 | let mut f = FmtBuf { b: &mut buf, n: 0 }; | ||
| 376 | let _ = fmt::write( | ||
| 377 | &mut f, | ||
| 378 | core::format_args!( | ||
| 379 | "{} at {:#x} (rip={:#x})", | ||
| 380 | if in_guard { | ||
| 381 | "stack overflow (guard page)" | ||
| 382 | } else { | ||
| 383 | "page fault" | ||
| 384 | }, | ||
| 385 | cr2, | ||
| 386 | rip | ||
| 387 | ), | ||
| 388 | ); | ||
| 389 | f.n | ||
| 390 | }; | ||
| 391 | abort_current(core::str::from_utf8(&buf[..n]).unwrap_or("task fault")) | ||
| 392 | } | ||
| 393 | |||
| 394 | // --------------------------------------------------------------------------- | ||
| 395 | // Introspection (shell: jobs / kill) | ||
| 396 | // --------------------------------------------------------------------------- | ||
| 397 | |||
| 398 | /// Snapshot the task table for `jobs` — one line per used slot. | ||
| 399 | pub fn jobs() -> Vec<String> { | ||
| 400 | let mut v = Vec::new(); | ||
| 401 | lock(); | ||
| 402 | unsafe { | ||
| 403 | for i in 0..MAX_TASKS { | ||
| 404 | let t = &TASKS[i]; | ||
| 405 | if t.name_len == 0 { | ||
| 406 | continue; // never used | ||
| 407 | } | ||
| 408 | let state = match t.state { | ||
| 409 | TaskState::Ready => "ready", | ||
| 410 | TaskState::Sleeping => "sleeping", | ||
| 411 | TaskState::Blocked => "blocked", | ||
| 412 | TaskState::Done => { | ||
| 413 | if t.panic_len > 0 { | ||
| 414 | "done (panicked)" | ||
| 415 | } else { | ||
| 416 | "done" | ||
| 417 | } | ||
| 418 | } | ||
| 419 | }; | ||
| 420 | let mut line = alloc::format!( | ||
| 421 | " {:>2} {:<12} {:<15} {:>6} ticks", | ||
| 422 | i, | ||
| 423 | name_str(i), | ||
| 424 | state, | ||
| 425 | t.ticks | ||
| 426 | ); | ||
| 427 | if t.panic_len > 0 { | ||
| 428 | let m = core::str::from_utf8(&t.panic[..t.panic_len as usize]).unwrap_or("?"); | ||
| 429 | line.push_str(" "); | ||
| 430 | line.push_str(m); | ||
| 431 | } | ||
| 432 | v.push(line); | ||
| 433 | } | ||
| 434 | } | ||
| 435 | unlock(); | ||
| 436 | v | ||
| 437 | } | ||
| 438 | |||
| 439 | /// Mark a task Done so it stops running and gets reaped at the next | ||
| 440 | /// spawn. The shell (0) and idle (1) are protected. | ||
| 441 | pub fn kill(id: usize) -> Result<(), &'static str> { | ||
| 442 | if id == SHELL_TID { | ||
| 443 | return Err("cannot kill the shell (task 0)"); | ||
| 444 | } | ||
| 445 | if id == IDLE_TID { | ||
| 446 | return Err("cannot kill the idle task (task 1)"); | ||
| 447 | } | ||
| 448 | if id >= MAX_TASKS { | ||
| 449 | return Err("no such task"); | ||
| 450 | } | ||
| 451 | lock(); | ||
| 452 | let alive = unsafe { | ||
| 453 | let t = &TASKS[id]; | ||
| 454 | t.name_len != 0 && t.state != TaskState::Done | ||
| 455 | }; | ||
| 456 | if !alive { | ||
| 457 | unlock(); | ||
| 458 | return Err("no such task"); | ||
| 459 | } | ||
| 460 | unsafe { | ||
| 461 | TASKS[id].state = TaskState::Done; | ||
| 462 | } | ||
| 463 | unlock(); | ||
| 464 | Ok(()) | ||
| 465 | } | ||
| 466 | |||
| 467 | pub fn name_str(id: usize) -> &'static str { | ||
| 468 | unsafe { | ||
| 469 | let t = &TASKS[id]; | ||
| 470 | if t.name_len == 0 { | ||
| 471 | return ""; | ||
| 472 | } | ||
| 473 | core::str::from_utf8(&t.name[..t.name_len as usize]).unwrap_or("") | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | // --------------------------------------------------------------------------- | ||
| 478 | // Init | ||
| 479 | // --------------------------------------------------------------------------- | ||
| 480 | |||
| 481 | /// Set up the task table: task 0 is the shell (boot stack), task 1 is | ||
| 482 | /// the idle `hlt` fallback. Called after `interrupts::init` (the tick | ||
| 483 | /// must be live so `sleep`/wake scheduling works). | ||
| 484 | pub fn init() { | ||
| 485 | unsafe { | ||
| 486 | let cs64: u64; | ||
| 487 | asm!("mov {0}, cs", out(reg) cs64, options(nostack, preserves_flags)); | ||
| 488 | KERNEL_CS = cs64 as u16; | ||
| 489 | let ss64: u64; | ||
| 490 | asm!("mov {0}, ss", out(reg) ss64, options(nostack, preserves_flags)); | ||
| 491 | KERNEL_SS = ss64 as u16; | ||
| 492 | |||
| 493 | TASKS[SHELL_TID] = Task { | ||
| 494 | name: { | ||
| 495 | let mut n = [0u8; 24]; | ||
| 496 | n[..5].copy_from_slice(b"shell"); | ||
| 497 | n | ||
| 498 | }, | ||
| 499 | name_len: 5, | ||
| 500 | state: TaskState::Ready, | ||
| 501 | saved_rsp: 0, // captured on the first switch-away | ||
| 502 | stack: 0, | ||
| 503 | guard: 0, | ||
| 504 | wake_at: 0, | ||
| 505 | ticks: 0, | ||
| 506 | panic: [0; PANIC_MAX], | ||
| 507 | panic_len: 0, | ||
| 508 | }; | ||
| 509 | spawn_at(IDLE_TID, idle_main, "idle"); | ||
| 510 | SCHED_READY.store(true, Ordering::Relaxed); | ||
| 511 | } | ||
| 512 | crate::serial_println!( | ||
| 513 | "sched: ready — {} slots, shell=task 0, idle=task 1", | ||
| 514 | MAX_TASKS | ||
| 515 | ); | ||
| 516 | crate::serial_println!( | ||
| 517 | "sched: trampoline={:#x} idle={:#x} emit={:#x}", | ||
| 518 | trampoline as *const () as usize, | ||
| 519 | idle_main as *const () as usize, | ||
| 520 | crate::klog::emit as *const () as usize | ||
| 521 | ); | ||
| 522 | } | ||
| 523 | |||
| 524 | fn idle_main() { | ||
| 525 | loop { | ||
| 526 | unsafe { | ||
| 527 | asm!("hlt", options(nostack, preserves_flags)); | ||
| 528 | } | ||
| 529 | } | ||
| 530 | } | ||
| 531 | |||
| 532 | struct FmtBuf<'a> { | ||
| 533 | b: &'a mut [u8], | ||
| 534 | n: usize, | ||
| 535 | } | ||
| 536 | |||
| 537 | impl fmt::Write for FmtBuf<'_> { | ||
| 538 | fn write_str(&mut self, s: &str) -> fmt::Result { | ||
| 539 | let m = s.len().min(self.b.len() - self.n); | ||
| 540 | self.b[self.n..self.n + m].copy_from_slice(&s.as_bytes()[..m]); | ||
| 541 | self.n += m; | ||
| 542 | Ok(()) | ||
| 543 | } | ||
| 544 | } | ||
