From 9fafdca636e1894532afea9562876de874ade859 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 11 Aug 2026 01:12:29 +0200 Subject: v0.5.10: change em-dashes to dashes --- shell/src/lib.rs | 196 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 112 insertions(+), 84 deletions(-) (limited to 'shell/src') diff --git a/shell/src/lib.rs b/shell/src/lib.rs index c9b7cdd..a54f338 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -//! Command shell — reads from PS/2 keyboard, writes to GOP + serial. +//! Command shell - reads from PS/2 keyboard, writes to GOP + serial. //! //! One canonical grammar for every command (ADR 0002): //! @@ -17,7 +17,7 @@ //! end of stream), `-g PAT` (smart-case literal substring), `-p` (pause //! at each screenful). Each may repeat. //! - **Quoting** (`"..."` groups + literalizes, `\x` escapes one char) -//! makes quoted/escaped text literal — never an operator, never split. +//! makes quoted/escaped text literal - never an operator, never split. //! - `>` and friends are gone; `>` is ordinary text. //! - ^D is EOF everywhere and never powers off; `poweroff`/`exit` are the //! only shutdown verbs. @@ -32,8 +32,13 @@ use rsnix::{gop_print, gop_println}; /// Run the shell forever. pub fn run() -> ! { + // Row 0 is the boot-marker status line (white-on-black, the last boot + // stage drawn during init). Real hardware has no serial console, so + // a freeze must leave that line visible - start the shell one row + // below instead of overwriting it. + gop_println!(); gop_println!( - "rs-dos v{} — type 'help' for commands", + "rs-dos v{} - type 'help' for commands", env!("CARGO_PKG_VERSION") ); term_println(&alloc::format!( @@ -74,8 +79,8 @@ pub fn run() -> ! { // Read one line from keyboard (tty canonical mode) match tty::read_line() { - LineInput::Interrupted => continue, // ^C — fresh prompt - LineInput::Eof => continue, // ^D on an empty line — fresh prompt (no poweroff) + LineInput::Interrupted => continue, // ^C - fresh prompt + LineInput::Eof => continue, // ^D on an empty line - fresh prompt (no poweroff) LineInput::Line(line) => { let trimmed = line.trim(); if trimmed.is_empty() { @@ -90,8 +95,8 @@ pub fn run() -> ! { // --------------------------------------------------------------------------- // Diagnostics and command output go straight to terminal + serial, never // through the filter pipeline and never into a file sink. These helpers -// write to the serial port directly — NOT via `serial_println!`, which -// feeds the klog ring — so user-facing shell output (prompts, echoes, +// write to the serial port directly - NOT via `serial_println!`, which +// feeds the klog ring - so user-facing shell output (prompts, echoes, // command results) never lands in the boot log. // --------------------------------------------------------------------------- @@ -181,11 +186,11 @@ fn is_keyword(s: &str) -> bool { #[derive(Clone)] enum Input { None, - /// `USE f` — read from a file. + /// `USE f` - read from a file. Use(String), - /// `TIL m` — read from the keyboard until an exact line equals `m`. + /// `TIL m` - read from the keyboard until an exact line equals `m`. Til(String), - /// `USE f TIL m` — read `f` until an exact line equals `m`. + /// `USE f TIL m` - read `f` until an exact line equals `m`. UseTil(String, String), } @@ -412,10 +417,10 @@ fn smart_match(pat: &str, line: &str) -> bool { /// Pager pause: terminal only, waits for a keypress; ^C aborts the stream. fn pager_wait() -> bool { - gop_print!("\n— more —"); + gop_print!("\n- more -"); match tty::read_byte() { 0x03 => { - // ^C — abort the stream (stream-abort role). + // ^C - abort the stream (stream-abort role). gop_println!(); false } @@ -519,7 +524,7 @@ impl Pipeline { } buf.push_back(l); } - // Held back — nothing reaches the downstream yet. + // Held back - nothing reaches the downstream yet. return true; } Filter::Grep(pat) => { @@ -697,7 +702,7 @@ fn execute(s: &str) { // path arguments is genuinely two input sources. if !parsed.args.is_empty() && !matches!(parsed.input, Input::Til(_)) { term_err(&alloc::format!( - "{}: input source keyword with path arguments is ambiguous — \ + "{}: input source keyword with path arguments is ambiguous - \ the paths are the input; drop the keyword, or to truncate them at a marker \ write `{} TIL `", cmd, @@ -756,44 +761,44 @@ write `{} TIL `", fn cmd_help(_p: &Parsed) { let mut pipe = Pipeline::new(&_p.filters, &_p.output); let text = "available commands: - help — show this message - echo — echo arguments - clear — clear screen - hello — print 'World!' - info — show system info - mem — show memory info - mmap — dump the UEFI memory map - stress — stress-test heap/page allocators - date — show current date/time (RTC) - pci — list PCI devices - rescan — re-scan for hotplugged/removed disks (also runs automatically) - cursor — report the on-screen mouse cursor (x, y, buttons) - mouse — watch the cursor live (x/y/buttons); ^C stops - mouse — route mouse events to task 's queue (cursor still tracks) - gfx — demo: draw a status bar + RGB boxes (bottom strip) - gfx check — pixel self-test: shadow, display push, cursor overlay - bg — spawn a background task (spinner, ticker, counter, boom, overflow) - msgrecv — spawn a task that blocks on its message queue and logs every + help - show this message + echo - echo arguments + clear - clear screen + hello - print 'World!' + info - show system info + mem - show memory info + mmap - dump the UEFI memory map + stress - stress-test heap/page allocators + date - show current date/time (RTC) + pci - list PCI devices + rescan - re-scan for hotplugged/removed disks (also runs automatically) + cursor - report the on-screen mouse cursor (x, y, buttons) + mouse - watch the cursor live (x/y/buttons); ^C stops + mouse - route mouse events to task 's queue (cursor still tracks) + gfx - demo: draw a status bar + RGB boxes (bottom strip) + gfx check - pixel self-test: shadow, display push, cursor overlay + bg - spawn a background task (spinner, ticker, counter, boom, overflow) + msgrecv - spawn a task that blocks on its message queue and logs every message to serial (ids from `jobs`) - send [args] — post a message to a task's queue: + send [args] - post a message to a task's queue: user · timer · mouse (1=L 2=R 4=M) key (enter esc tab space bksp up down left right home end pgup pgdn del ctrl-c f1..f12, or a single char) - jobs — list tasks (id, name, state, ticks, q=pending messages) - sleep — block the shell for ms; background tasks keep running - kill — stop a background task (id from jobs) - demo — spawn spinner + ticker + counter - ls — list a directory - cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) - klog — kernel boot log (cat /dev/klog; WRITING persists it) - hexdump — hex-dump a file or input stream - mkdir — create a directory - touch — create an empty file - rm — remove a file or empty dir - edit — full-screen editor (^S save, ^X exit, ^W search) - halt — halt the system (hlt loop) - poweroff — shut down via ACPI (ResetSystem) - exit — alias for poweroff + jobs - list tasks (id, name, state, ticks, q=pending messages) + sleep - block the shell for ms; background tasks keep running + kill - stop a background task (id from jobs) + demo - spawn spinner + ticker + counter + ls - list a directory + cat - print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) + klog - kernel boot log (cat /dev/klog; WRITING persists it) + hexdump - hex-dump a file or input stream + mkdir - create a directory + touch - create an empty file + rm - remove a file or empty dir + edit - full-screen editor (^S save, ^X exit, ^W search) + halt - halt the system (hlt loop) + poweroff - shut down via ACPI (ResetSystem) + exit - alias for poweroff redirection keywords (uppercase, after args): USE/USING read command input from a file (cat/hexdump only) @@ -830,7 +835,7 @@ fn cmd_hello(p: &Parsed) { pipe.end(); } -/// Deliberately fault on an unmapped address — test hook that verifies +/// Deliberately fault on an unmapped address - test hook that verifies /// the fault-dump path (screen + serial) halts cleanly instead of /// triple-faulting. The system stops; the log must show the dump. /// Canonical-but-unmapped address (below 2^47, above any RAM/MMIO): the @@ -842,7 +847,7 @@ fn cmd_fault() { } } -/// Deliberately clobber a live heap block's back-pointer, then free it — +/// Deliberately clobber a live heap block's back-pointer, then free it - /// test hook that proves the allocator's corruption guard fires with a /// readable report (instead of a #GP inside `__rust_dealloc`). The /// system stops; the log must show `!!! heap corruption in dealloc`. @@ -865,7 +870,7 @@ fn cmd_crashlog() { term_println("crashlog: done"); } -/// Set the display-store stall (cycles per word) — diagnostics knob that +/// Set the display-store stall (cycles per word) - diagnostics knob that /// simulates the per-write cost of an uncached real-GPU framebuffer so /// the preemptive scheduler races long scroll pushes like on real /// hardware. `stall 0` disables (default; QEMU RAM framebuffers are @@ -884,7 +889,7 @@ fn cmd_info(p: &Parsed) { let mut pipe = Pipeline::new(&p.filters, &p.output); let mut s = String::from("rs-dos v"); s.push_str(env!("CARGO_PKG_VERSION")); - s.push_str(" — rsnix kernel"); + s.push_str(" - rsnix kernel"); let _ = pipe.line(&s); let _ = pipe.line("arch: x86_64, freestanding, no userspace"); pipe.end(); @@ -921,7 +926,7 @@ fn cmd_ls(p: &Parsed) { } // --------------------------------------------------------------------------- -// klog — kernel log (thin alias for `cat /dev/klog`) +// klog - kernel log (thin alias for `cat /dev/klog`) // --------------------------------------------------------------------------- fn cmd_klog(p: &Parsed) { @@ -929,7 +934,7 @@ fn cmd_klog(p: &Parsed) { term_err("klog: usage: klog [-h N] [-t N] [-g PAT] [-p] [WRITING | TO ]"); return; } - // Alias for `cat /dev/klog` — same filters and redirection, so + // Alias for `cat /dev/klog` - same filters and redirection, so // `klog -t 5` shows the last 5 log lines and `klog WRITING // /disk/.../boot.log` persists the boot log. Input keywords were // already rejected (klog does not accept input). @@ -944,11 +949,11 @@ fn cmd_klog(p: &Parsed) { } // --------------------------------------------------------------------------- -// cat — file and stream consumers +// cat - file and stream consumers // --------------------------------------------------------------------------- /// How a file read ended: read to EOF, stopped at the TIL marker, or -/// aborted (^C / head kill) — the caller should stop processing paths. +/// aborted (^C / head kill) - the caller should stop processing paths. enum ReadResult { Done, Marker, @@ -978,7 +983,7 @@ fn cmd_cat(p: &Parsed) { if p.args.is_empty() { cat_stdin(&mut pipe, Some(marker)); } else { - // `cat TIL ` — the paths are the input + // `cat TIL ` - the paths are the input // source, truncated at the first marker line. for path in &p.args { match cat_file_until(&mut pipe, path, marker) { @@ -996,7 +1001,7 @@ fn cmd_cat(p: &Parsed) { } /// Read lines from the tty until ^C/^D (or an exact marker line, when -/// `marker` is Some — the marker itself is not part of the input). +/// `marker` is Some - the marker itself is not part of the input). fn cat_stdin(pipe: &mut Pipeline, marker: Option<&str>) -> bool { loop { match tty::read_line() { @@ -1097,7 +1102,7 @@ fn cat_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> ReadResult { } // --------------------------------------------------------------------------- -// hexdump — hex-dump files and input streams +// hexdump - hex-dump files and input streams // --------------------------------------------------------------------------- struct HexCtx<'a> { @@ -1181,7 +1186,7 @@ fn cmd_hexdump(p: &Parsed) { if p.args.is_empty() { hexdump_stdin(&mut pipe, Some(marker)); } else { - // `hexdump TIL ` — the paths are the input + // `hexdump TIL ` - the paths are the input // source, truncated at the first marker line. for path in &p.args { match hexdump_file_until(&mut pipe, path, marker) { @@ -1236,7 +1241,7 @@ fn hexdump_file(pipe: &mut Pipeline, path: &str) -> bool { } /// Hex-dump lines from the tty until ^C/^D (or an exact marker line, -/// when `marker` is Some — the marker itself is not dumped). +/// when `marker` is Some - the marker itself is not dumped). fn hexdump_stdin(pipe: &mut Pipeline, marker: Option<&str>) { let mut ctx = HexCtx { pipe, @@ -1476,7 +1481,7 @@ fn cmd_mem(p: &Parsed) { } // --------------------------------------------------------------------------- -// mmap — dump every entry of the UEFI memory map with type names +// mmap - dump every entry of the UEFI memory map with type names // --------------------------------------------------------------------------- fn cmd_mmap(p: &Parsed) { let (map, key) = rsnix::memory::get_map(); @@ -1504,7 +1509,7 @@ fn cmd_mmap(p: &Parsed) { } // --------------------------------------------------------------------------- -// stress — allocate blocks of KB, hold them, then free all. +// stress - allocate blocks of KB, hold them, then free all. // Pushes the heap past its base region so growth into the page allocator // is exercised; also verifies a clean return to the free-list after. // --------------------------------------------------------------------------- @@ -1590,7 +1595,7 @@ fn cmd_heapdbg(_p: &Parsed) { for l in rsnix::memory::freelist_lines(12) { term_println(&l); } - // fb vs heap overlap — OptiPlex crash hypothesis: the display's + // fb vs heap overlap - OptiPlex crash hypothesis: the display's // framebuffer may sit inside the heap region (GPU BAR not reserved in // the EFI map), so display writes clobber live heap allocations. if let Some((fb, fsz)) = rsnix::gop::framebuffer_region() { @@ -1621,7 +1626,7 @@ fn cmd_heapdbg(_p: &Parsed) { term_println(&alloc::format!("probe mem: {}", s)); // Scan the live shadow payload for 0x00aaaaaa words. NOTE: this is // the GRAY text color (rgb(170,170,170) = PALETTE[7] = fg 7), NOT a - // firmware scrub — the "0x00AAAAAA fill" in the OptiPlex crash dumps + // firmware scrub - the "0x00AAAAAA fill" in the OptiPlex crash dumps // is just rendered text pixels. The scan's real value: untouched // shadow rows must be 0; non-zero far rows = out-of-bounds writes. if let Some((b, size, _)) = rsnix::gop::shadow_block_state() { @@ -1743,16 +1748,16 @@ fn report_rescan((da, dr, ma, mu, na, nr): (usize, usize, usize, usize, usize, u fn cmd_rescan(_p: &Parsed) { if report_rescan(rescan_all()) { - term_println("rescan done — /disk updated"); + term_println("rescan done - /disk updated"); } else { - term_println("rescan done — nothing changed"); + term_println("rescan done - nothing changed"); } } /// Mouse front-end. The cursor task owns the PS/2 ring, so this /// command *watches the on-screen cursor* (position + buttons, printed /// only on change) until ^C. With a task id it also routes every event -/// to that task's queue — the window-manager input path — clearing the +/// to that task's queue - the window-manager input path - clearing the /// route on exit. fn cmd_mouse(p: &Parsed) { let target: Option = p.args.first().and_then(|s| s.parse::().ok()); @@ -1763,14 +1768,14 @@ fn cmd_mouse(p: &Parsed) { } rsnix::cursor::set_forward(Some(id)); term_println(&alloc::format!( - "mouse: routing events to task {} (cursor still tracks) — ^C to stop", + "mouse: routing events to task {} (cursor still tracks) - ^C to stop", id )); } else if !rsnix::ps2::mouse_present() { - term_err("mouse: no PS/2 mouse at boot — cat /dev/mouse still works"); + term_err("mouse: no PS/2 mouse at boot - cat /dev/mouse still works"); return; } else { - term_println("mouse: watching cursor — move the mouse, ^C to stop"); + term_println("mouse: watching cursor - move the mouse, ^C to stop"); } let mut last = (usize::MAX, usize::MAX, u8::MAX); loop { @@ -1932,7 +1937,7 @@ fn gfx_check() { fn cmd_bg(p: &Parsed) { let Some(name) = p.args.first() else { - term_err("bg: needs a task name — spinner, ticker, counter, boom, overflow"); + term_err("bg: needs a task name - spinner, ticker, counter, boom, overflow"); return; }; let entry: fn() = match name.as_str() { @@ -1975,7 +1980,7 @@ fn cmd_jobs(p: &Parsed) { fn cmd_msgrecv(_p: &Parsed) { match rsnix::sched::spawn(task_msgrecv, "msgrecv") { Some(id) => term_println(&alloc::format!( - "msgrecv: task {} listening — post with: send {} …", + "msgrecv: task {} listening - post with: send {} …", id, id )), @@ -1984,7 +1989,7 @@ fn cmd_msgrecv(_p: &Parsed) { } /// The recv demo body: block forever, log each message. Headless by -/// design (serial only — it never touches tty/vt/fs, per ADR 0003). +/// design (serial only - it never touches tty/vt/fs, per ADR 0003). fn task_msgrecv() { loop { let msg = rsnix::sched::recv_blocking(); @@ -1996,7 +2001,7 @@ fn task_msgrecv() { } } -/// `send [args]` — post a TaskMsg to a task's queue. This +/// `send [args]` - post a TaskMsg to a task's queue. This /// is the shell-side knob for the primitive the window manager will use /// to route input; types: user , timer, mouse /// (1=L 2=R 4=M), key (enter esc tab space bksp up down left @@ -2006,14 +2011,14 @@ fn cmd_send(p: &Parsed) { let id: usize = match args.next().and_then(|s| s.parse().ok()) { Some(v) => v, None => { - term_err("send: needs a task id — jobs lists them, msgrecv spawns a listener"); + term_err("send: needs a task id - jobs lists them, msgrecv spawns a listener"); return; } }; let kind = match args.next() { Some(k) => k.as_str(), None => { - term_err("send: needs a type — user | timer | mouse | key "); + term_err("send: needs a type - user | timer | mouse | key "); return; } }; @@ -2052,7 +2057,7 @@ fn cmd_send(p: &Parsed) { } }, _ => { - term_err("send: unknown type — user | timer | mouse | key "); + term_err("send: unknown type - user | timer | mouse | key "); return; } }; @@ -2113,7 +2118,7 @@ fn cmd_sleep(p: &Parsed) { } }; term_println(&alloc::format!( - "sleep: blocking shell for {} ms — background tasks keep running", + "sleep: blocking shell for {} ms - background tasks keep running", ms )); rsnix::sched::sleep_ms(ms); @@ -2146,7 +2151,7 @@ fn cmd_demo(_p: &Parsed) { } } term_println(&alloc::format!( - "demo: spawned {} task(s) — spinner 250ms, ticker 500ms, counter 1000ms (try jobs)", + "demo: spawned {} task(s) - spinner 250ms, ticker 500ms, counter 1000ms (try jobs)", n )); } @@ -2184,7 +2189,7 @@ fn task_boom() { panic!("boom: deliberate task panic"); } -/// Recurses until the guard page faults — the task should die with a +/// Recurses until the guard page faults - the task should die with a /// "stack overflow" panic while the shell keeps running. fn task_overflow() { #[allow(unconditional_recursion)] @@ -2196,20 +2201,35 @@ fn task_overflow() { recurse(0); } +fn usb_if_label(c: u8, s: u8, p: u8) -> &'static str { + if c == 0x0C && s == 0x03 { + match p { + 0x00 => "UHCI", + 0x10 => "OHCI", + 0x20 => "EHCI", + 0x30 => "xHCI", + _ => "USB*", + } + } else { + "" + } +} + fn cmd_pci(p: &Parsed) { let devices = rsnix::pci::probe(); let mut pipe = Pipeline::new(&p.filters, &p.output); let _ = pipe.line(&alloc::format!("pci: {} device(s)", devices.len())); - let _ = pipe.line(" bus dev fn class vendor:device"); + let _ = pipe.line(" bus dev fn class vendor:device if"); for d in &devices { let l = alloc::format!( - " {:02x}:{:02x}.{} {:<16} {:04x}:{:04x}", + " {:02x}:{:02x}.{} {:<16} {:04x}:{:04x} {}", d.bus, d.device, d.func, rsnix::pci::class_name(d.class, d.subclass), d.vendor, - d.device_id + d.device_id, + usb_if_label(d.class, d.subclass, d.prog_if) ); if !pipe.line(&l) { break; @@ -2234,6 +2254,14 @@ fn cmd_usb(p: &Parsed) { } } pipe.end(); + // EHCI (USB2) controller status - the real hardware path. + let mut pipe = Pipeline::new(&p.filters, &p.output); + for l in rsnix::ehci::diagnose().lines() { + if !pipe.line(l) { + break; + } + } + pipe.end(); } fn cmd_poweroff() -> ! { -- cgit v1.2.3