summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-11 01:12:29 +0200
committersalmonik <salmonik@tuta.io>2026-08-11 01:12:58 +0200
commit9fafdca636e1894532afea9562876de874ade859 (patch)
treed8d862cb89247b24927554c842380a5b9853f4ee
parentv0.5.10: USB HID mouse via polled xHCI (rsnix/src/usb.rs) (diff)
downloadrs-dos-9fafdca636e1894532afea9562876de874ade859.tar.gz
rs-dos-9fafdca636e1894532afea9562876de874ade859.zip
v0.5.10: change em-dashes to dashes
-rw-r--r--shell/src/lib.rs196
1 files changed, 112 insertions, 84 deletions
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 @@
1#![no_std] 1#![no_std]
2 2
3//! Command shell — reads from PS/2 keyboard, writes to GOP + serial. 3//! Command shell - reads from PS/2 keyboard, writes to GOP + serial.
4//! 4//!
5//! One canonical grammar for every command (ADR 0002): 5//! One canonical grammar for every command (ADR 0002):
6//! 6//!
@@ -17,7 +17,7 @@
17//! end of stream), `-g PAT` (smart-case literal substring), `-p` (pause 17//! end of stream), `-g PAT` (smart-case literal substring), `-p` (pause
18//! at each screenful). Each may repeat. 18//! at each screenful). Each may repeat.
19//! - **Quoting** (`"..."` groups + literalizes, `\x` escapes one char) 19//! - **Quoting** (`"..."` groups + literalizes, `\x` escapes one char)
20//! makes quoted/escaped text literal — never an operator, never split. 20//! makes quoted/escaped text literal - never an operator, never split.
21//! - `>` and friends are gone; `>` is ordinary text. 21//! - `>` and friends are gone; `>` is ordinary text.
22//! - ^D is EOF everywhere and never powers off; `poweroff`/`exit` are the 22//! - ^D is EOF everywhere and never powers off; `poweroff`/`exit` are the
23//! only shutdown verbs. 23//! only shutdown verbs.
@@ -32,8 +32,13 @@ use rsnix::{gop_print, gop_println};
32 32
33/// Run the shell forever. 33/// Run the shell forever.
34pub fn run() -> ! { 34pub fn run() -> ! {
35 // Row 0 is the boot-marker status line (white-on-black, the last boot
36 // stage drawn during init). Real hardware has no serial console, so
37 // a freeze must leave that line visible - start the shell one row
38 // below instead of overwriting it.
39 gop_println!();
35 gop_println!( 40 gop_println!(
36 "rs-dos v{} — type 'help' for commands", 41 "rs-dos v{} - type 'help' for commands",
37 env!("CARGO_PKG_VERSION") 42 env!("CARGO_PKG_VERSION")
38 ); 43 );
39 term_println(&alloc::format!( 44 term_println(&alloc::format!(
@@ -74,8 +79,8 @@ pub fn run() -> ! {
74 79
75 // Read one line from keyboard (tty canonical mode) 80 // Read one line from keyboard (tty canonical mode)
76 match tty::read_line() { 81 match tty::read_line() {
77 LineInput::Interrupted => continue, // ^C — fresh prompt 82 LineInput::Interrupted => continue, // ^C - fresh prompt
78 LineInput::Eof => continue, // ^D on an empty line — fresh prompt (no poweroff) 83 LineInput::Eof => continue, // ^D on an empty line - fresh prompt (no poweroff)
79 LineInput::Line(line) => { 84 LineInput::Line(line) => {
80 let trimmed = line.trim(); 85 let trimmed = line.trim();
81 if trimmed.is_empty() { 86 if trimmed.is_empty() {
@@ -90,8 +95,8 @@ pub fn run() -> ! {
90// --------------------------------------------------------------------------- 95// ---------------------------------------------------------------------------
91// Diagnostics and command output go straight to terminal + serial, never 96// Diagnostics and command output go straight to terminal + serial, never
92// through the filter pipeline and never into a file sink. These helpers 97// through the filter pipeline and never into a file sink. These helpers
93// write to the serial port directly — NOT via `serial_println!`, which 98// write to the serial port directly - NOT via `serial_println!`, which
94// feeds the klog ring — so user-facing shell output (prompts, echoes, 99// feeds the klog ring - so user-facing shell output (prompts, echoes,
95// command results) never lands in the boot log. 100// command results) never lands in the boot log.
96// --------------------------------------------------------------------------- 101// ---------------------------------------------------------------------------
97 102
@@ -181,11 +186,11 @@ fn is_keyword(s: &str) -> bool {
181#[derive(Clone)] 186#[derive(Clone)]
182enum Input { 187enum Input {
183 None, 188 None,
184 /// `USE f` — read from a file. 189 /// `USE f` - read from a file.
185 Use(String), 190 Use(String),
186 /// `TIL m` — read from the keyboard until an exact line equals `m`. 191 /// `TIL m` - read from the keyboard until an exact line equals `m`.
187 Til(String), 192 Til(String),
188 /// `USE f TIL m` — read `f` until an exact line equals `m`. 193 /// `USE f TIL m` - read `f` until an exact line equals `m`.
189 UseTil(String, String), 194 UseTil(String, String),
190} 195}
191 196
@@ -412,10 +417,10 @@ fn smart_match(pat: &str, line: &str) -> bool {
412 417
413/// Pager pause: terminal only, waits for a keypress; ^C aborts the stream. 418/// Pager pause: terminal only, waits for a keypress; ^C aborts the stream.
414fn pager_wait() -> bool { 419fn pager_wait() -> bool {
415 gop_print!("\n— more —"); 420 gop_print!("\n- more -");
416 match tty::read_byte() { 421 match tty::read_byte() {
417 0x03 => { 422 0x03 => {
418 // ^C — abort the stream (stream-abort role). 423 // ^C - abort the stream (stream-abort role).
419 gop_println!(); 424 gop_println!();
420 false 425 false
421 } 426 }
@@ -519,7 +524,7 @@ impl Pipeline {
519 } 524 }
520 buf.push_back(l); 525 buf.push_back(l);
521 } 526 }
522 // Held back — nothing reaches the downstream yet. 527 // Held back - nothing reaches the downstream yet.
523 return true; 528 return true;
524 } 529 }
525 Filter::Grep(pat) => { 530 Filter::Grep(pat) => {
@@ -697,7 +702,7 @@ fn execute(s: &str) {
697 // path arguments is genuinely two input sources. 702 // path arguments is genuinely two input sources.
698 if !parsed.args.is_empty() && !matches!(parsed.input, Input::Til(_)) { 703 if !parsed.args.is_empty() && !matches!(parsed.input, Input::Til(_)) {
699 term_err(&alloc::format!( 704 term_err(&alloc::format!(
700 "{}: input source keyword with path arguments is ambiguous — \ 705 "{}: input source keyword with path arguments is ambiguous - \
701the paths are the input; drop the keyword, or to truncate them at a marker \ 706the paths are the input; drop the keyword, or to truncate them at a marker \
702write `{} <path> TIL <marker>`", 707write `{} <path> TIL <marker>`",
703 cmd, 708 cmd,
@@ -756,44 +761,44 @@ write `{} <path> TIL <marker>`",
756fn cmd_help(_p: &Parsed) { 761fn cmd_help(_p: &Parsed) {
757 let mut pipe = Pipeline::new(&_p.filters, &_p.output); 762 let mut pipe = Pipeline::new(&_p.filters, &_p.output);
758 let text = "available commands: 763 let text = "available commands:
759 help — show this message 764 help - show this message
760 echo — echo arguments 765 echo - echo arguments
761 clear — clear screen 766 clear - clear screen
762 hello — print 'World!' 767 hello - print 'World!'
763 info — show system info 768 info - show system info
764 mem — show memory info 769 mem - show memory info
765 mmap — dump the UEFI memory map 770 mmap - dump the UEFI memory map
766 stress — stress-test heap/page allocators 771 stress - stress-test heap/page allocators
767 date — show current date/time (RTC) 772 date - show current date/time (RTC)
768 pci — list PCI devices 773 pci - list PCI devices
769 rescan — re-scan for hotplugged/removed disks (also runs automatically) 774 rescan - re-scan for hotplugged/removed disks (also runs automatically)
770 cursor — report the on-screen mouse cursor (x, y, buttons) 775 cursor - report the on-screen mouse cursor (x, y, buttons)
771 mouse — watch the cursor live (x/y/buttons); ^C stops 776 mouse - watch the cursor live (x/y/buttons); ^C stops
772 mouse <id> — route mouse events to task <id>'s queue (cursor still tracks) 777 mouse <id> - route mouse events to task <id>'s queue (cursor still tracks)
773 gfx — demo: draw a status bar + RGB boxes (bottom strip) 778 gfx - demo: draw a status bar + RGB boxes (bottom strip)
774 gfx check — pixel self-test: shadow, display push, cursor overlay 779 gfx check - pixel self-test: shadow, display push, cursor overlay
775 bg <name> — spawn a background task (spinner, ticker, counter, boom, overflow) 780 bg <name> - spawn a background task (spinner, ticker, counter, boom, overflow)
776 msgrecv — spawn a task that blocks on its message queue and logs every 781 msgrecv - spawn a task that blocks on its message queue and logs every
777 message to serial (ids from `jobs`) 782 message to serial (ids from `jobs`)
778 send <id> <type> [args] — post a message to a task's queue: 783 send <id> <type> [args] - post a message to a task's queue:
779 user <n> · timer · mouse <dx> <dy> <btn> (1=L 2=R 4=M) 784 user <n> · timer · mouse <dx> <dy> <btn> (1=L 2=R 4=M)
780 key <name> (enter esc tab space bksp up down left right home 785 key <name> (enter esc tab space bksp up down left right home
781 end pgup pgdn del ctrl-c f1..f12, or a single char) 786 end pgup pgdn del ctrl-c f1..f12, or a single char)
782 jobs — list tasks (id, name, state, ticks, q=pending messages) 787 jobs - list tasks (id, name, state, ticks, q=pending messages)
783 sleep <ms> — block the shell for ms; background tasks keep running 788 sleep <ms> - block the shell for ms; background tasks keep running
784 kill <id> — stop a background task (id from jobs) 789 kill <id> - stop a background task (id from jobs)
785 demo — spawn spinner + ticker + counter 790 demo - spawn spinner + ticker + counter
786 ls — list a directory 791 ls - list a directory
787 cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) 792 cat - print files (cat USE a TO b copies a→b; cat TIL END is a heredoc)
788 klog — kernel boot log (cat /dev/klog; WRITING <dst> persists it) 793 klog - kernel boot log (cat /dev/klog; WRITING <dst> persists it)
789 hexdump — hex-dump a file or input stream 794 hexdump - hex-dump a file or input stream
790 mkdir — create a directory 795 mkdir - create a directory
791 touch — create an empty file 796 touch - create an empty file
792 rm — remove a file or empty dir 797 rm - remove a file or empty dir
793 edit — full-screen editor (^S save, ^X exit, ^W search) 798 edit - full-screen editor (^S save, ^X exit, ^W search)
794 halt — halt the system (hlt loop) 799 halt - halt the system (hlt loop)
795 poweroff — shut down via ACPI (ResetSystem) 800 poweroff - shut down via ACPI (ResetSystem)
796 exit — alias for poweroff 801 exit - alias for poweroff
797 802
798redirection keywords (uppercase, after args): 803redirection keywords (uppercase, after args):
799 USE/USING <src> read command input from a file (cat/hexdump only) 804 USE/USING <src> read command input from a file (cat/hexdump only)
@@ -830,7 +835,7 @@ fn cmd_hello(p: &Parsed) {
830 pipe.end(); 835 pipe.end();
831} 836}
832 837
833/// Deliberately fault on an unmapped address — test hook that verifies 838/// Deliberately fault on an unmapped address - test hook that verifies
834/// the fault-dump path (screen + serial) halts cleanly instead of 839/// the fault-dump path (screen + serial) halts cleanly instead of
835/// triple-faulting. The system stops; the log must show the dump. 840/// triple-faulting. The system stops; the log must show the dump.
836/// Canonical-but-unmapped address (below 2^47, above any RAM/MMIO): the 841/// Canonical-but-unmapped address (below 2^47, above any RAM/MMIO): the
@@ -842,7 +847,7 @@ fn cmd_fault() {
842 } 847 }
843} 848}
844 849
845/// Deliberately clobber a live heap block's back-pointer, then free it — 850/// Deliberately clobber a live heap block's back-pointer, then free it -
846/// test hook that proves the allocator's corruption guard fires with a 851/// test hook that proves the allocator's corruption guard fires with a
847/// readable report (instead of a #GP inside `__rust_dealloc`). The 852/// readable report (instead of a #GP inside `__rust_dealloc`). The
848/// system stops; the log must show `!!! heap corruption in dealloc`. 853/// system stops; the log must show `!!! heap corruption in dealloc`.
@@ -865,7 +870,7 @@ fn cmd_crashlog() {
865 term_println("crashlog: done"); 870 term_println("crashlog: done");
866} 871}
867 872
868/// Set the display-store stall (cycles per word) — diagnostics knob that 873/// Set the display-store stall (cycles per word) - diagnostics knob that
869/// simulates the per-write cost of an uncached real-GPU framebuffer so 874/// simulates the per-write cost of an uncached real-GPU framebuffer so
870/// the preemptive scheduler races long scroll pushes like on real 875/// the preemptive scheduler races long scroll pushes like on real
871/// hardware. `stall 0` disables (default; QEMU RAM framebuffers are 876/// hardware. `stall 0` disables (default; QEMU RAM framebuffers are
@@ -884,7 +889,7 @@ fn cmd_info(p: &Parsed) {
884 let mut pipe = Pipeline::new(&p.filters, &p.output); 889 let mut pipe = Pipeline::new(&p.filters, &p.output);
885 let mut s = String::from("rs-dos v"); 890 let mut s = String::from("rs-dos v");
886 s.push_str(env!("CARGO_PKG_VERSION")); 891 s.push_str(env!("CARGO_PKG_VERSION"));
887 s.push_str(" — rsnix kernel"); 892 s.push_str(" - rsnix kernel");
888 let _ = pipe.line(&s); 893 let _ = pipe.line(&s);
889 let _ = pipe.line("arch: x86_64, freestanding, no userspace"); 894 let _ = pipe.line("arch: x86_64, freestanding, no userspace");
890 pipe.end(); 895 pipe.end();
@@ -921,7 +926,7 @@ fn cmd_ls(p: &Parsed) {
921} 926}
922 927
923// --------------------------------------------------------------------------- 928// ---------------------------------------------------------------------------
924// klog — kernel log (thin alias for `cat /dev/klog`) 929// klog - kernel log (thin alias for `cat /dev/klog`)
925// --------------------------------------------------------------------------- 930// ---------------------------------------------------------------------------
926 931
927fn cmd_klog(p: &Parsed) { 932fn cmd_klog(p: &Parsed) {
@@ -929,7 +934,7 @@ fn cmd_klog(p: &Parsed) {
929 term_err("klog: usage: klog [-h N] [-t N] [-g PAT] [-p] [WRITING <dst> | TO <dst>]"); 934 term_err("klog: usage: klog [-h N] [-t N] [-g PAT] [-p] [WRITING <dst> | TO <dst>]");
930 return; 935 return;
931 } 936 }
932 // Alias for `cat /dev/klog` — same filters and redirection, so 937 // Alias for `cat /dev/klog` - same filters and redirection, so
933 // `klog -t 5` shows the last 5 log lines and `klog WRITING 938 // `klog -t 5` shows the last 5 log lines and `klog WRITING
934 // /disk/.../boot.log` persists the boot log. Input keywords were 939 // /disk/.../boot.log` persists the boot log. Input keywords were
935 // already rejected (klog does not accept input). 940 // already rejected (klog does not accept input).
@@ -944,11 +949,11 @@ fn cmd_klog(p: &Parsed) {
944} 949}
945 950
946// --------------------------------------------------------------------------- 951// ---------------------------------------------------------------------------
947// cat — file and stream consumers 952// cat - file and stream consumers
948// --------------------------------------------------------------------------- 953// ---------------------------------------------------------------------------
949 954
950/// How a file read ended: read to EOF, stopped at the TIL marker, or 955/// How a file read ended: read to EOF, stopped at the TIL marker, or
951/// aborted (^C / head kill) — the caller should stop processing paths. 956/// aborted (^C / head kill) - the caller should stop processing paths.
952enum ReadResult { 957enum ReadResult {
953 Done, 958 Done,
954 Marker, 959 Marker,
@@ -978,7 +983,7 @@ fn cmd_cat(p: &Parsed) {
978 if p.args.is_empty() { 983 if p.args.is_empty() {
979 cat_stdin(&mut pipe, Some(marker)); 984 cat_stdin(&mut pipe, Some(marker));
980 } else { 985 } else {
981 // `cat <path> TIL <marker>` — the paths are the input 986 // `cat <path> TIL <marker>` - the paths are the input
982 // source, truncated at the first marker line. 987 // source, truncated at the first marker line.
983 for path in &p.args { 988 for path in &p.args {
984 match cat_file_until(&mut pipe, path, marker) { 989 match cat_file_until(&mut pipe, path, marker) {
@@ -996,7 +1001,7 @@ fn cmd_cat(p: &Parsed) {
996} 1001}
997 1002
998/// Read lines from the tty until ^C/^D (or an exact marker line, when 1003/// Read lines from the tty until ^C/^D (or an exact marker line, when
999/// `marker` is Some — the marker itself is not part of the input). 1004/// `marker` is Some - the marker itself is not part of the input).
1000fn cat_stdin(pipe: &mut Pipeline, marker: Option<&str>) -> bool { 1005fn cat_stdin(pipe: &mut Pipeline, marker: Option<&str>) -> bool {
1001 loop { 1006 loop {
1002 match tty::read_line() { 1007 match tty::read_line() {
@@ -1097,7 +1102,7 @@ fn cat_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> ReadResult {
1097} 1102}
1098 1103
1099// --------------------------------------------------------------------------- 1104// ---------------------------------------------------------------------------
1100// hexdump — hex-dump files and input streams 1105// hexdump - hex-dump files and input streams
1101// --------------------------------------------------------------------------- 1106// ---------------------------------------------------------------------------
1102 1107
1103struct HexCtx<'a> { 1108struct HexCtx<'a> {
@@ -1181,7 +1186,7 @@ fn cmd_hexdump(p: &Parsed) {
1181 if p.args.is_empty() { 1186 if p.args.is_empty() {
1182 hexdump_stdin(&mut pipe, Some(marker)); 1187 hexdump_stdin(&mut pipe, Some(marker));
1183 } else { 1188 } else {
1184 // `hexdump <path> TIL <marker>` — the paths are the input 1189 // `hexdump <path> TIL <marker>` - the paths are the input
1185 // source, truncated at the first marker line. 1190 // source, truncated at the first marker line.
1186 for path in &p.args { 1191 for path in &p.args {
1187 match hexdump_file_until(&mut pipe, path, marker) { 1192 match hexdump_file_until(&mut pipe, path, marker) {
@@ -1236,7 +1241,7 @@ fn hexdump_file(pipe: &mut Pipeline, path: &str) -> bool {
1236} 1241}
1237 1242
1238/// Hex-dump lines from the tty until ^C/^D (or an exact marker line, 1243/// Hex-dump lines from the tty until ^C/^D (or an exact marker line,
1239/// when `marker` is Some — the marker itself is not dumped). 1244/// when `marker` is Some - the marker itself is not dumped).
1240fn hexdump_stdin(pipe: &mut Pipeline, marker: Option<&str>) { 1245fn hexdump_stdin(pipe: &mut Pipeline, marker: Option<&str>) {
1241 let mut ctx = HexCtx { 1246 let mut ctx = HexCtx {
1242 pipe, 1247 pipe,
@@ -1476,7 +1481,7 @@ fn cmd_mem(p: &Parsed) {
1476} 1481}
1477 1482
1478// --------------------------------------------------------------------------- 1483// ---------------------------------------------------------------------------
1479// mmap — dump every entry of the UEFI memory map with type names 1484// mmap - dump every entry of the UEFI memory map with type names
1480// --------------------------------------------------------------------------- 1485// ---------------------------------------------------------------------------
1481fn cmd_mmap(p: &Parsed) { 1486fn cmd_mmap(p: &Parsed) {
1482 let (map, key) = rsnix::memory::get_map(); 1487 let (map, key) = rsnix::memory::get_map();
@@ -1504,7 +1509,7 @@ fn cmd_mmap(p: &Parsed) {
1504} 1509}
1505 1510
1506// --------------------------------------------------------------------------- 1511// ---------------------------------------------------------------------------
1507// stress — allocate <chunks> blocks of <kb> KB, hold them, then free all. 1512// stress - allocate <chunks> blocks of <kb> KB, hold them, then free all.
1508// Pushes the heap past its base region so growth into the page allocator 1513// Pushes the heap past its base region so growth into the page allocator
1509// is exercised; also verifies a clean return to the free-list after. 1514// is exercised; also verifies a clean return to the free-list after.
1510// --------------------------------------------------------------------------- 1515// ---------------------------------------------------------------------------
@@ -1590,7 +1595,7 @@ fn cmd_heapdbg(_p: &Parsed) {
1590 for l in rsnix::memory::freelist_lines(12) { 1595 for l in rsnix::memory::freelist_lines(12) {
1591 term_println(&l); 1596 term_println(&l);
1592 } 1597 }
1593 // fb vs heap overlap — OptiPlex crash hypothesis: the display's 1598 // fb vs heap overlap - OptiPlex crash hypothesis: the display's
1594 // framebuffer may sit inside the heap region (GPU BAR not reserved in 1599 // framebuffer may sit inside the heap region (GPU BAR not reserved in
1595 // the EFI map), so display writes clobber live heap allocations. 1600 // the EFI map), so display writes clobber live heap allocations.
1596 if let Some((fb, fsz)) = rsnix::gop::framebuffer_region() { 1601 if let Some((fb, fsz)) = rsnix::gop::framebuffer_region() {
@@ -1621,7 +1626,7 @@ fn cmd_heapdbg(_p: &Parsed) {
1621 term_println(&alloc::format!("probe mem: {}", s)); 1626 term_println(&alloc::format!("probe mem: {}", s));
1622 // Scan the live shadow payload for 0x00aaaaaa words. NOTE: this is 1627 // Scan the live shadow payload for 0x00aaaaaa words. NOTE: this is
1623 // the GRAY text color (rgb(170,170,170) = PALETTE[7] = fg 7), NOT a 1628 // the GRAY text color (rgb(170,170,170) = PALETTE[7] = fg 7), NOT a
1624 // firmware scrub — the "0x00AAAAAA fill" in the OptiPlex crash dumps 1629 // firmware scrub - the "0x00AAAAAA fill" in the OptiPlex crash dumps
1625 // is just rendered text pixels. The scan's real value: untouched 1630 // is just rendered text pixels. The scan's real value: untouched
1626 // shadow rows must be 0; non-zero far rows = out-of-bounds writes. 1631 // shadow rows must be 0; non-zero far rows = out-of-bounds writes.
1627 if let Some((b, size, _)) = rsnix::gop::shadow_block_state() { 1632 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
1743 1748
1744fn cmd_rescan(_p: &Parsed) { 1749fn cmd_rescan(_p: &Parsed) {
1745 if report_rescan(rescan_all()) { 1750 if report_rescan(rescan_all()) {
1746 term_println("rescan done — /disk updated"); 1751 term_println("rescan done - /disk updated");
1747 } else { 1752 } else {
1748 term_println("rescan done — nothing changed"); 1753 term_println("rescan done - nothing changed");
1749 } 1754 }
1750} 1755}
1751 1756
1752/// Mouse front-end. The cursor task owns the PS/2 ring, so this 1757/// Mouse front-end. The cursor task owns the PS/2 ring, so this
1753/// command *watches the on-screen cursor* (position + buttons, printed 1758/// command *watches the on-screen cursor* (position + buttons, printed
1754/// only on change) until ^C. With a task id it also routes every event 1759/// only on change) until ^C. With a task id it also routes every event
1755/// to that task's queue — the window-manager input path — clearing the 1760/// to that task's queue - the window-manager input path - clearing the
1756/// route on exit. 1761/// route on exit.
1757fn cmd_mouse(p: &Parsed) { 1762fn cmd_mouse(p: &Parsed) {
1758 let target: Option<usize> = p.args.first().and_then(|s| s.parse::<usize>().ok()); 1763 let target: Option<usize> = p.args.first().and_then(|s| s.parse::<usize>().ok());
@@ -1763,14 +1768,14 @@ fn cmd_mouse(p: &Parsed) {
1763 } 1768 }
1764 rsnix::cursor::set_forward(Some(id)); 1769 rsnix::cursor::set_forward(Some(id));
1765 term_println(&alloc::format!( 1770 term_println(&alloc::format!(
1766 "mouse: routing events to task {} (cursor still tracks) — ^C to stop", 1771 "mouse: routing events to task {} (cursor still tracks) - ^C to stop",
1767 id 1772 id
1768 )); 1773 ));
1769 } else if !rsnix::ps2::mouse_present() { 1774 } else if !rsnix::ps2::mouse_present() {
1770 term_err("mouse: no PS/2 mouse at boot — cat /dev/mouse still works"); 1775 term_err("mouse: no PS/2 mouse at boot - cat /dev/mouse still works");
1771 return; 1776 return;
1772 } else { 1777 } else {
1773 term_println("mouse: watching cursor — move the mouse, ^C to stop"); 1778 term_println("mouse: watching cursor - move the mouse, ^C to stop");
1774 } 1779 }
1775 let mut last = (usize::MAX, usize::MAX, u8::MAX); 1780 let mut last = (usize::MAX, usize::MAX, u8::MAX);
1776 loop { 1781 loop {
@@ -1932,7 +1937,7 @@ fn gfx_check() {
1932 1937
1933fn cmd_bg(p: &Parsed) { 1938fn cmd_bg(p: &Parsed) {
1934 let Some(name) = p.args.first() else { 1939 let Some(name) = p.args.first() else {
1935 term_err("bg: needs a task name — spinner, ticker, counter, boom, overflow"); 1940 term_err("bg: needs a task name - spinner, ticker, counter, boom, overflow");
1936 return; 1941 return;
1937 }; 1942 };
1938 let entry: fn() = match name.as_str() { 1943 let entry: fn() = match name.as_str() {
@@ -1975,7 +1980,7 @@ fn cmd_jobs(p: &Parsed) {
1975fn cmd_msgrecv(_p: &Parsed) { 1980fn cmd_msgrecv(_p: &Parsed) {
1976 match rsnix::sched::spawn(task_msgrecv, "msgrecv") { 1981 match rsnix::sched::spawn(task_msgrecv, "msgrecv") {
1977 Some(id) => term_println(&alloc::format!( 1982 Some(id) => term_println(&alloc::format!(
1978 "msgrecv: task {} listening — post with: send {} <type> …", 1983 "msgrecv: task {} listening - post with: send {} <type> …",
1979 id, 1984 id,
1980 id 1985 id
1981 )), 1986 )),
@@ -1984,7 +1989,7 @@ fn cmd_msgrecv(_p: &Parsed) {
1984} 1989}
1985 1990
1986/// The recv demo body: block forever, log each message. Headless by 1991/// The recv demo body: block forever, log each message. Headless by
1987/// design (serial only — it never touches tty/vt/fs, per ADR 0003). 1992/// design (serial only - it never touches tty/vt/fs, per ADR 0003).
1988fn task_msgrecv() { 1993fn task_msgrecv() {
1989 loop { 1994 loop {
1990 let msg = rsnix::sched::recv_blocking(); 1995 let msg = rsnix::sched::recv_blocking();
@@ -1996,7 +2001,7 @@ fn task_msgrecv() {
1996 } 2001 }
1997} 2002}
1998 2003
1999/// `send <id> <type> [args]` — post a TaskMsg to a task's queue. This 2004/// `send <id> <type> [args]` - post a TaskMsg to a task's queue. This
2000/// is the shell-side knob for the primitive the window manager will use 2005/// is the shell-side knob for the primitive the window manager will use
2001/// to route input; types: user <n>, timer, mouse <dx> <dy> <btn> 2006/// to route input; types: user <n>, timer, mouse <dx> <dy> <btn>
2002/// (1=L 2=R 4=M), key <name> (enter esc tab space bksp up down left 2007/// (1=L 2=R 4=M), key <name> (enter esc tab space bksp up down left
@@ -2006,14 +2011,14 @@ fn cmd_send(p: &Parsed) {
2006 let id: usize = match args.next().and_then(|s| s.parse().ok()) { 2011 let id: usize = match args.next().and_then(|s| s.parse().ok()) {
2007 Some(v) => v, 2012 Some(v) => v,
2008 None => { 2013 None => {
2009 term_err("send: needs a task id — jobs lists them, msgrecv spawns a listener"); 2014 term_err("send: needs a task id - jobs lists them, msgrecv spawns a listener");
2010 return; 2015 return;
2011 } 2016 }
2012 }; 2017 };
2013 let kind = match args.next() { 2018 let kind = match args.next() {
2014 Some(k) => k.as_str(), 2019 Some(k) => k.as_str(),
2015 None => { 2020 None => {
2016 term_err("send: needs a type — user <n> | timer | mouse <dx> <dy> <btn> | key <name>"); 2021 term_err("send: needs a type - user <n> | timer | mouse <dx> <dy> <btn> | key <name>");
2017 return; 2022 return;
2018 } 2023 }
2019 }; 2024 };
@@ -2052,7 +2057,7 @@ fn cmd_send(p: &Parsed) {
2052 } 2057 }
2053 }, 2058 },
2054 _ => { 2059 _ => {
2055 term_err("send: unknown type — user <n> | timer | mouse <dx> <dy> <btn> | key <name>"); 2060 term_err("send: unknown type - user <n> | timer | mouse <dx> <dy> <btn> | key <name>");
2056 return; 2061 return;
2057 } 2062 }
2058 }; 2063 };
@@ -2113,7 +2118,7 @@ fn cmd_sleep(p: &Parsed) {
2113 } 2118 }
2114 }; 2119 };
2115 term_println(&alloc::format!( 2120 term_println(&alloc::format!(
2116 "sleep: blocking shell for {} ms — background tasks keep running", 2121 "sleep: blocking shell for {} ms - background tasks keep running",
2117 ms 2122 ms
2118 )); 2123 ));
2119 rsnix::sched::sleep_ms(ms); 2124 rsnix::sched::sleep_ms(ms);
@@ -2146,7 +2151,7 @@ fn cmd_demo(_p: &Parsed) {
2146 } 2151 }
2147 } 2152 }
2148 term_println(&alloc::format!( 2153 term_println(&alloc::format!(
2149 "demo: spawned {} task(s) — spinner 250ms, ticker 500ms, counter 1000ms (try jobs)", 2154 "demo: spawned {} task(s) - spinner 250ms, ticker 500ms, counter 1000ms (try jobs)",
2150 n 2155 n
2151 )); 2156 ));
2152} 2157}
@@ -2184,7 +2189,7 @@ fn task_boom() {
2184 panic!("boom: deliberate task panic"); 2189 panic!("boom: deliberate task panic");
2185} 2190}
2186 2191
2187/// Recurses until the guard page faults — the task should die with a 2192/// Recurses until the guard page faults - the task should die with a
2188/// "stack overflow" panic while the shell keeps running. 2193/// "stack overflow" panic while the shell keeps running.
2189fn task_overflow() { 2194fn task_overflow() {
2190 #[allow(unconditional_recursion)] 2195 #[allow(unconditional_recursion)]
@@ -2196,20 +2201,35 @@ fn task_overflow() {
2196 recurse(0); 2201 recurse(0);
2197} 2202}
2198 2203
2204fn usb_if_label(c: u8, s: u8, p: u8) -> &'static str {
2205 if c == 0x0C && s == 0x03 {
2206 match p {
2207 0x00 => "UHCI",
2208 0x10 => "OHCI",
2209 0x20 => "EHCI",
2210 0x30 => "xHCI",
2211 _ => "USB*",
2212 }
2213 } else {
2214 ""
2215 }
2216}
2217
2199fn cmd_pci(p: &Parsed) { 2218fn cmd_pci(p: &Parsed) {
2200 let devices = rsnix::pci::probe(); 2219 let devices = rsnix::pci::probe();
2201 let mut pipe = Pipeline::new(&p.filters, &p.output); 2220 let mut pipe = Pipeline::new(&p.filters, &p.output);
2202 let _ = pipe.line(&alloc::format!("pci: {} device(s)", devices.len())); 2221 let _ = pipe.line(&alloc::format!("pci: {} device(s)", devices.len()));
2203 let _ = pipe.line(" bus dev fn class vendor:device"); 2222 let _ = pipe.line(" bus dev fn class vendor:device if");
2204 for d in &devices { 2223 for d in &devices {
2205 let l = alloc::format!( 2224 let l = alloc::format!(
2206 " {:02x}:{:02x}.{} {:<16} {:04x}:{:04x}", 2225 " {:02x}:{:02x}.{} {:<16} {:04x}:{:04x} {}",
2207 d.bus, 2226 d.bus,
2208 d.device, 2227 d.device,
2209 d.func, 2228 d.func,
2210 rsnix::pci::class_name(d.class, d.subclass), 2229 rsnix::pci::class_name(d.class, d.subclass),
2211 d.vendor, 2230 d.vendor,
2212 d.device_id 2231 d.device_id,
2232 usb_if_label(d.class, d.subclass, d.prog_if)
2213 ); 2233 );
2214 if !pipe.line(&l) { 2234 if !pipe.line(&l) {
2215 break; 2235 break;
@@ -2234,6 +2254,14 @@ fn cmd_usb(p: &Parsed) {
2234 } 2254 }
2235 } 2255 }
2236 pipe.end(); 2256 pipe.end();
2257 // EHCI (USB2) controller status - the real hardware path.
2258 let mut pipe = Pipeline::new(&p.filters, &p.output);
2259 for l in rsnix::ehci::diagnose().lines() {
2260 if !pipe.line(l) {
2261 break;
2262 }
2263 }
2264 pipe.end();
2237} 2265}
2238 2266
2239fn cmd_poweroff() -> ! { 2267fn cmd_poweroff() -> ! {