diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-11 01:12:29 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-11 01:12:58 +0200 |
| commit | 9fafdca636e1894532afea9562876de874ade859 (patch) | |
| tree | d8d862cb89247b24927554c842380a5b9853f4ee | |
| parent | v0.5.10: USB HID mouse via polled xHCI (rsnix/src/usb.rs) (diff) | |
| download | rs-dos-9fafdca636e1894532afea9562876de874ade859.tar.gz rs-dos-9fafdca636e1894532afea9562876de874ade859.zip | |
v0.5.10: change em-dashes to dashes
| -rw-r--r-- | shell/src/lib.rs | 196 |
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. |
| 34 | pub fn run() -> ! { | 34 | pub 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)] |
| 182 | enum Input { | 187 | enum 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. |
| 414 | fn pager_wait() -> bool { | 419 | fn 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 - \ |
| 701 | the paths are the input; drop the keyword, or to truncate them at a marker \ | 706 | the paths are the input; drop the keyword, or to truncate them at a marker \ |
| 702 | write `{} <path> TIL <marker>`", | 707 | write `{} <path> TIL <marker>`", |
| 703 | cmd, | 708 | cmd, |
| @@ -756,44 +761,44 @@ write `{} <path> TIL <marker>`", | |||
| 756 | fn cmd_help(_p: &Parsed) { | 761 | fn 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 | ||
| 798 | redirection keywords (uppercase, after args): | 803 | redirection 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 | ||
| 927 | fn cmd_klog(p: &Parsed) { | 932 | fn 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. |
| 952 | enum ReadResult { | 957 | enum 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). |
| 1000 | fn cat_stdin(pipe: &mut Pipeline, marker: Option<&str>) -> bool { | 1005 | fn 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 | ||
| 1103 | struct HexCtx<'a> { | 1108 | struct 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). |
| 1240 | fn hexdump_stdin(pipe: &mut Pipeline, marker: Option<&str>) { | 1245 | fn 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 | // --------------------------------------------------------------------------- |
| 1481 | fn cmd_mmap(p: &Parsed) { | 1486 | fn 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 | ||
| 1744 | fn cmd_rescan(_p: &Parsed) { | 1749 | fn 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. |
| 1757 | fn cmd_mouse(p: &Parsed) { | 1762 | fn 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 | ||
| 1933 | fn cmd_bg(p: &Parsed) { | 1938 | fn 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) { | |||
| 1975 | fn cmd_msgrecv(_p: &Parsed) { | 1980 | fn 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). |
| 1988 | fn task_msgrecv() { | 1993 | fn 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. |
| 2189 | fn task_overflow() { | 2194 | fn 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 | ||
| 2204 | fn 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 | |||
| 2199 | fn cmd_pci(p: &Parsed) { | 2218 | fn 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 | ||
| 2239 | fn cmd_poweroff() -> ! { | 2267 | fn cmd_poweroff() -> ! { |
