diff options
| -rw-r--r-- | CONTEXT.md | 36 | ||||
| -rw-r--r-- | rsnix/src/fs.rs | 8 | ||||
| -rw-r--r-- | rsnix/src/klog.rs | 130 | ||||
| -rw-r--r-- | rsnix/src/lib.rs | 1 | ||||
| -rw-r--r-- | rsnix/src/serial.rs | 11 | ||||
| -rw-r--r-- | shell/src/lib.rs | 64 |
6 files changed, 226 insertions, 24 deletions
| @@ -255,6 +255,34 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target: | |||
| 255 | monitor (`--sock`, US layout, named keys for punctuation; bare | 255 | monitor (`--sock`, US layout, named keys for punctuation; bare |
| 256 | `-`/`.`/`/` are not valid sendkey names) | 256 | `-`/`.`/`/` are not valid sendkey names) |
| 257 | 257 | ||
| 258 | ✅ **`klog` kernel log ring** — a RAM ring of the last 128 serial log | ||
| 259 | lines (128 chars each, ~16 KB), fed by `serial_println!` (one choke | ||
| 260 | point — every boot/low-level message captured for free). Sequence | ||
| 261 | numbers per line + wrap/drop accounting; a dump header reports how | ||
| 262 | many lines were lost to wrap. Exposed as the `/dev/klog` char | ||
| 263 | device; the shell `klog` command is a thin alias for `cat | ||
| 264 | /dev/klog` (same filters/redirection), so `klog -t 5`, `klog -g | ||
| 265 | fat`, and `klog WRITING /disk/…/boot.log` (persists the boot log) | ||
| 266 | all work. The ring never allocates (const-constructible static); | ||
| 267 | only the dump allocates, at shell time. `klog` rejects positional | ||
| 268 | args (usage) and input keywords ("does not accept input"). | ||
| 269 | |||
| 270 | **Design consequence: shell output must not feed the ring.** The | ||
| 271 | plan's "hook `serial_println!`" only works if that macro is | ||
| 272 | kernel-logging-only — but the shell printed prompts/errors/results | ||
| 273 | through it, which would have flooded the boot log with echo/cat | ||
| 274 | output (a `cat /dev/mem` would wrap the ring). The shell now | ||
| 275 | writes user-facing output (banner, prompt, `term_print`/ | ||
| 276 | `term_println`, pipeline sink, halt/poweroff) directly to the | ||
| 277 | serial port via `serial::write_str`; tty echo already did. Only | ||
| 278 | kernel messages (rsnix modules + `rsdos/main.rs`) go through | ||
| 279 | `serial_println!` now. Verified in QEMU: 28 boot lines captured | ||
| 280 | (seq 0..27, first `=== rsnix boot ===`, last `boot: entering | ||
| 281 | shell`), `echo hello` output absent from the ring, `klog WRITING` | ||
| 282 | persists a boot log (mtype-verified), wrap behavior verified with a | ||
| 283 | temporary 140-line boot loop (128 kept / 40 dropped, seq 40..167, | ||
| 284 | oldest live line correct — loop reverted after the test). | ||
| 285 | |||
| 258 | ## Known issues / perf notes | 286 | ## Known issues / perf notes |
| 259 | 287 | ||
| 260 | - **Scroll cost** — scrolling still shifts the whole framebuffer (a | 288 | - **Scroll cost** — scrolling still shifts the whole framebuffer (a |
| @@ -289,14 +317,6 @@ Workspace of four crates, all built for the `x86_64-unknown-uefi` target: | |||
| 289 | 317 | ||
| 290 | ## Pending decisions (agreed, not yet implemented) | 318 | ## Pending decisions (agreed, not yet implemented) |
| 291 | 319 | ||
| 292 | - **`klog` kernel log ring**: capture all boot/low-level messages by | ||
| 293 | appending to a RAM ring buffer inside `serial_println!` (one choke | ||
| 294 | point, every existing message captured for free). Ring: 128 lines × | ||
| 295 | 128 chars ≈ 16 KB, sequence numbers, wrap/drop accounting. | ||
| 296 | - **`klog` command** = thin alias for `cat /dev/klog` plus filters | ||
| 297 | `-h` (head) and `-t` (tail); no log levels, no timestamps, nothing | ||
| 298 | fancier. `/dev/klog` is a char device so `klog WRITING /disk/…/boot.log` | ||
| 299 | persists a boot log via the existing redirection. | ||
| 300 | - **Disk recognition scope — keep simple for now**: no pre-EBS UEFI | 320 | - **Disk recognition scope — keep simple for now**: no pre-EBS UEFI |
| 301 | block-device census, no GPT/MBR partition parsing, no NVMe driver. | 321 | block-device census, no GPT/MBR partition parsing, no NVMe driver. |
| 302 | (Revisit later: Linux-style recognition = PCI scan → ahci/nvme → | 322 | (Revisit later: Linux-style recognition = PCI scan → ahci/nvme → |
diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs index a71e51a..063560a 100644 --- a/rsnix/src/fs.rs +++ b/rsnix/src/fs.rs | |||
| @@ -41,6 +41,7 @@ enum CharDevice { | |||
| 41 | Pci, | 41 | Pci, |
| 42 | Version, | 42 | Version, |
| 43 | Meminfo, | 43 | Meminfo, |
| 44 | Klog, | ||
| 44 | Null, | 45 | Null, |
| 45 | Zero, | 46 | Zero, |
| 46 | Console, | 47 | Console, |
| @@ -141,6 +142,12 @@ pub fn init() -> (usize, usize) { | |||
| 141 | NodeData::Char(CharDevice::Meminfo), | 142 | NodeData::Char(CharDevice::Meminfo), |
| 142 | ); | 143 | ); |
| 143 | add( | 144 | add( |
| 145 | "klog", | ||
| 146 | Some(dev), | ||
| 147 | FileType::Char, | ||
| 148 | NodeData::Char(CharDevice::Klog), | ||
| 149 | ); | ||
| 150 | add( | ||
| 144 | "null", | 151 | "null", |
| 145 | Some(dev), | 152 | Some(dev), |
| 146 | FileType::Char, | 153 | FileType::Char, |
| @@ -650,6 +657,7 @@ fn dev_text(dev: CharDevice, _pos: u64) -> String { | |||
| 650 | CharDevice::Pci => pci_listing(), | 657 | CharDevice::Pci => pci_listing(), |
| 651 | CharDevice::Version => version_text(), | 658 | CharDevice::Version => version_text(), |
| 652 | CharDevice::Meminfo => meminfo_text(), | 659 | CharDevice::Meminfo => meminfo_text(), |
| 660 | CharDevice::Klog => crate::klog::dump(), | ||
| 653 | CharDevice::Null | CharDevice::Console | CharDevice::Serial => String::new(), | 661 | CharDevice::Null | CharDevice::Console | CharDevice::Serial => String::new(), |
| 654 | CharDevice::Zero => String::new(), | 662 | CharDevice::Zero => String::new(), |
| 655 | } | 663 | } |
diff --git a/rsnix/src/klog.rs b/rsnix/src/klog.rs new file mode 100644 index 0000000..1e23460 --- /dev/null +++ b/rsnix/src/klog.rs | |||
| @@ -0,0 +1,130 @@ | |||
| 1 | // --------------------------------------------------------------------------- | ||
| 2 | // klog — kernel log ring buffer. | ||
| 3 | // | ||
| 4 | // A RAM ring of the last MAX_LINES serial log lines, fed by | ||
| 5 | // `serial_println!` (one choke point, so every boot/low-level message is | ||
| 6 | // captured for free). Exposed to the shell as the `/dev/klog` char | ||
| 7 | // device, so `klog` (alias for `cat /dev/klog`) shows the boot sequence | ||
| 8 | // and `klog WRITING /disk/.../boot.log` persists it via normal | ||
| 9 | // redirection. | ||
| 10 | // | ||
| 11 | // Line-based: the ring stores whole lines up to MAX_LINE bytes; longer | ||
| 12 | // lines are truncated. Each line gets a sequence number; when the ring | ||
| 13 | // wraps it drops the oldest line and counts it, so a dump can report how | ||
| 14 | // much history was lost. The ring never allocates (const-constructible | ||
| 15 | // static), so it works from the very first log line; only `dump()` | ||
| 16 | // allocates, and that is called at shell time. | ||
| 17 | // --------------------------------------------------------------------------- | ||
| 18 | |||
| 19 | extern crate alloc; | ||
| 20 | |||
| 21 | use alloc::string::String; | ||
| 22 | |||
| 23 | pub const MAX_LINES: usize = 128; | ||
| 24 | pub const MAX_LINE: usize = 128; | ||
| 25 | |||
| 26 | struct Ring { | ||
| 27 | /// Sequence counter for the next line to be appended (total ever). | ||
| 28 | seq: u64, | ||
| 29 | /// Lines dropped because the ring was full (wrap overwrites). | ||
| 30 | dropped: u64, | ||
| 31 | /// Live lines currently in the ring (0..=MAX_LINES). | ||
| 32 | count: usize, | ||
| 33 | /// Slot the next line will be written to (oldest once full). | ||
| 34 | next: usize, | ||
| 35 | lens: [u8; MAX_LINES], | ||
| 36 | bytes: [[u8; MAX_LINE]; MAX_LINES], | ||
| 37 | } | ||
| 38 | |||
| 39 | static mut RING: Ring = Ring { | ||
| 40 | seq: 0, | ||
| 41 | dropped: 0, | ||
| 42 | count: 0, | ||
| 43 | next: 0, | ||
| 44 | lens: [0; MAX_LINES], | ||
| 45 | bytes: [[0; MAX_LINE]; MAX_LINES], | ||
| 46 | }; | ||
| 47 | |||
| 48 | fn ring() -> &'static mut Ring { | ||
| 49 | unsafe { &mut *core::ptr::addr_of_mut!(RING) } | ||
| 50 | } | ||
| 51 | |||
| 52 | /// Append one line to the ring (line-oriented; newlines are stripped). | ||
| 53 | /// Truncated at MAX_LINE bytes. Never allocates, never logs. | ||
| 54 | pub fn append(line: &str) { | ||
| 55 | let r = ring(); | ||
| 56 | let b = line.as_bytes(); | ||
| 57 | let n = b.len().min(MAX_LINE); | ||
| 58 | if r.count == MAX_LINES { | ||
| 59 | r.dropped += 1; // overwrite the oldest line | ||
| 60 | } else { | ||
| 61 | r.count += 1; | ||
| 62 | } | ||
| 63 | r.bytes[r.next][..n].copy_from_slice(&b[..n]); | ||
| 64 | r.lens[r.next] = n as u8; | ||
| 65 | r.next = (r.next + 1) % MAX_LINES; | ||
| 66 | r.seq += 1; | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Append the formatted output of a `serial_println!` — the macro hook. | ||
| 70 | /// `fmt::Write` may deliver the line in several chunks, so accumulate | ||
| 71 | /// into one line buffer first, then append the whole line. | ||
| 72 | pub fn append_fmt(args: core::fmt::Arguments<'_>) { | ||
| 73 | let mut line = LineBuf::new(); | ||
| 74 | let _ = core::fmt::write(&mut line, args); | ||
| 75 | append(line.as_str()); | ||
| 76 | } | ||
| 77 | |||
| 78 | struct LineBuf { | ||
| 79 | bytes: [u8; MAX_LINE], | ||
| 80 | len: usize, | ||
| 81 | } | ||
| 82 | |||
| 83 | impl LineBuf { | ||
| 84 | fn new() -> Self { | ||
| 85 | LineBuf { | ||
| 86 | bytes: [0; MAX_LINE], | ||
| 87 | len: 0, | ||
| 88 | } | ||
| 89 | } | ||
| 90 | fn as_str(&self) -> &str { | ||
| 91 | core::str::from_utf8(&self.bytes[..self.len]).unwrap_or("") | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | impl core::fmt::Write for LineBuf { | ||
| 96 | fn write_str(&mut self, s: &str) -> core::fmt::Result { | ||
| 97 | let n = s.len().min(MAX_LINE - self.len); | ||
| 98 | self.bytes[self.len..self.len + n].copy_from_slice(&s.as_bytes()[..n]); | ||
| 99 | self.len += n; | ||
| 100 | Ok(()) | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | /// The whole log as text: a header with the wrap/drop accounting, then | ||
| 105 | /// one line per entry, each prefixed with its sequence number. | ||
| 106 | pub fn dump() -> String { | ||
| 107 | let r = ring(); | ||
| 108 | let mut out = String::new(); | ||
| 109 | if r.count == 0 { | ||
| 110 | out += "klog: no messages logged yet\n"; | ||
| 111 | return out; | ||
| 112 | } | ||
| 113 | let first = r.seq - r.count as u64; | ||
| 114 | let last = r.seq - 1; | ||
| 115 | out += &alloc::format!( | ||
| 116 | "klog: {} line(s) kept, {} dropped on wrap, seq {}..{}\n", | ||
| 117 | r.count, | ||
| 118 | r.dropped, | ||
| 119 | first, | ||
| 120 | last | ||
| 121 | ); | ||
| 122 | let start = (r.next + MAX_LINES - r.count) % MAX_LINES; | ||
| 123 | for i in 0..r.count { | ||
| 124 | let idx = (start + i) % MAX_LINES; | ||
| 125 | let n = r.lens[idx] as usize; | ||
| 126 | let text = core::str::from_utf8(&r.bytes[idx][..n]).unwrap_or(""); | ||
| 127 | out += &alloc::format!("{:06} {}\n", first + i as u64, text); | ||
| 128 | } | ||
| 129 | out | ||
| 130 | } | ||
diff --git a/rsnix/src/lib.rs b/rsnix/src/lib.rs index fc823ea..a2d41da 100644 --- a/rsnix/src/lib.rs +++ b/rsnix/src/lib.rs | |||
| @@ -8,6 +8,7 @@ pub mod font; | |||
| 8 | pub mod fs; | 8 | pub mod fs; |
| 9 | pub mod gop; | 9 | pub mod gop; |
| 10 | pub mod interrupts; | 10 | pub mod interrupts; |
| 11 | pub mod klog; | ||
| 11 | pub mod memory; | 12 | pub mod memory; |
| 12 | pub mod paging; | 13 | pub mod paging; |
| 13 | pub mod pci; | 14 | pub mod pci; |
diff --git a/rsnix/src/serial.rs b/rsnix/src/serial.rs index 03fa570..484a114 100644 --- a/rsnix/src/serial.rs +++ b/rsnix/src/serial.rs | |||
| @@ -91,12 +91,21 @@ macro_rules! serial_print { | |||
| 91 | }}; | 91 | }}; |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | /// Prints a line to serial and appends it to the kernel log ring | ||
| 95 | /// (klog — see /dev/klog). Every kernel-side message funnels through | ||
| 96 | /// here, so the ring captures the whole boot sequence for free. | ||
| 97 | /// User-facing shell output must NOT use this macro (shell output would | ||
| 98 | /// pollute the boot log); it writes to the serial port directly. | ||
| 94 | #[macro_export] | 99 | #[macro_export] |
| 95 | macro_rules! serial_println { | 100 | macro_rules! serial_println { |
| 96 | () => { $crate::serial::write_str("\r\n"); }; | 101 | () => { |
| 102 | $crate::serial::write_str("\r\n"); | ||
| 103 | $crate::klog::append_fmt(core::format_args!("")); | ||
| 104 | }; | ||
| 97 | ($($arg:tt)*) => {{ | 105 | ($($arg:tt)*) => {{ |
| 98 | $crate::serial_print!($($arg)*); | 106 | $crate::serial_print!($($arg)*); |
| 99 | $crate::serial::write_str("\r\n"); | 107 | $crate::serial::write_str("\r\n"); |
| 108 | $crate::klog::append_fmt(core::format_args!($($arg)*)); | ||
| 100 | }}; | 109 | }}; |
| 101 | } | 110 | } |
| 102 | 111 | ||
diff --git a/shell/src/lib.rs b/shell/src/lib.rs index cf46cb0..ddd8688 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs | |||
| @@ -28,7 +28,7 @@ use alloc::collections::VecDeque; | |||
| 28 | use alloc::string::{String, ToString}; | 28 | use alloc::string::{String, ToString}; |
| 29 | use alloc::vec::Vec; | 29 | use alloc::vec::Vec; |
| 30 | use rsnix::tty::{self, LineInput}; | 30 | use rsnix::tty::{self, LineInput}; |
| 31 | use rsnix::{gop_print, gop_println, serial_print, serial_println}; | 31 | 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() -> ! { |
| @@ -36,18 +36,17 @@ pub fn run() -> ! { | |||
| 36 | "rs-dos v{} — type 'help' for commands", | 36 | "rs-dos v{} — type 'help' for commands", |
| 37 | env!("CARGO_PKG_VERSION") | 37 | env!("CARGO_PKG_VERSION") |
| 38 | ); | 38 | ); |
| 39 | serial_println!( | 39 | term_println(&alloc::format!( |
| 40 | "========== rs-dos v{} ==========", | 40 | "========== rs-dos v{} ==========", |
| 41 | env!("CARGO_PKG_VERSION") | 41 | env!("CARGO_PKG_VERSION") |
| 42 | ); | 42 | )); |
| 43 | 43 | ||
| 44 | loop { | 44 | loop { |
| 45 | // Push any deferred scrolls so the prompt renders on a current | 45 | // Push any deferred scrolls so the prompt renders on a current |
| 46 | // framebuffer (e.g. after a streaming command's batch of output). | 46 | // framebuffer (e.g. after a streaming command's batch of output). |
| 47 | rsnix::vt::sync_scroll(); | 47 | rsnix::vt::sync_scroll(); |
| 48 | // Print prompt | 48 | // Print prompt |
| 49 | gop_print!("> "); | 49 | term_print("> "); |
| 50 | serial_print!("> "); | ||
| 51 | 50 | ||
| 52 | // Read one line from keyboard (tty canonical mode) | 51 | // Read one line from keyboard (tty canonical mode) |
| 53 | match tty::read_line() { | 52 | match tty::read_line() { |
| @@ -65,13 +64,26 @@ pub fn run() -> ! { | |||
| 65 | } | 64 | } |
| 66 | 65 | ||
| 67 | // --------------------------------------------------------------------------- | 66 | // --------------------------------------------------------------------------- |
| 68 | // Diagnostics go straight to terminal + serial, never through the filter | 67 | // Diagnostics and command output go straight to terminal + serial, never |
| 69 | // pipeline and never into a file sink. | 68 | // through the filter pipeline and never into a file sink. These helpers |
| 69 | // write to the serial port directly — NOT via `serial_println!`, which | ||
| 70 | // feeds the klog ring — so user-facing shell output (prompts, echoes, | ||
| 71 | // command results) never lands in the boot log. | ||
| 70 | // --------------------------------------------------------------------------- | 72 | // --------------------------------------------------------------------------- |
| 71 | 73 | ||
| 74 | fn term_print(s: &str) { | ||
| 75 | gop_print!("{}", s); | ||
| 76 | rsnix::serial::write_str(s); | ||
| 77 | } | ||
| 78 | |||
| 79 | fn term_println(s: &str) { | ||
| 80 | term_print(s); | ||
| 81 | gop_println!(); | ||
| 82 | rsnix::serial::write_str("\r\n"); | ||
| 83 | } | ||
| 84 | |||
| 72 | fn term_err(msg: &str) { | 85 | fn term_err(msg: &str) { |
| 73 | gop_println!("{}", msg); | 86 | term_println(msg); |
| 74 | serial_println!("{}", msg); | ||
| 75 | } | 87 | } |
| 76 | 88 | ||
| 77 | // --------------------------------------------------------------------------- | 89 | // --------------------------------------------------------------------------- |
| @@ -541,8 +553,7 @@ impl Pipeline { | |||
| 541 | fn emit(&mut self, l: &str) { | 553 | fn emit(&mut self, l: &str) { |
| 542 | match &self.sink { | 554 | match &self.sink { |
| 543 | OutSink::Term => { | 555 | OutSink::Term => { |
| 544 | gop_println!("{}", l); | 556 | term_println(l); |
| 545 | serial_println!("{}", l); | ||
| 546 | } | 557 | } |
| 547 | OutSink::File { .. } => {} | 558 | OutSink::File { .. } => {} |
| 548 | } | 559 | } |
| @@ -672,6 +683,7 @@ write `{} <path> TIL <marker>`", | |||
| 672 | "pci" => cmd_pci(&parsed), | 683 | "pci" => cmd_pci(&parsed), |
| 673 | "ls" => cmd_ls(&parsed), | 684 | "ls" => cmd_ls(&parsed), |
| 674 | "cat" => cmd_cat(&parsed), | 685 | "cat" => cmd_cat(&parsed), |
| 686 | "klog" => cmd_klog(&parsed), | ||
| 675 | "hexdump" => cmd_hexdump(&parsed), | 687 | "hexdump" => cmd_hexdump(&parsed), |
| 676 | "mkdir" => cmd_mkdir(&parsed), | 688 | "mkdir" => cmd_mkdir(&parsed), |
| 677 | "touch" => cmd_touch(&parsed), | 689 | "touch" => cmd_touch(&parsed), |
| @@ -700,6 +712,7 @@ fn cmd_help(_p: &Parsed) { | |||
| 700 | pci — list PCI devices | 712 | pci — list PCI devices |
| 701 | ls — list a directory | 713 | ls — list a directory |
| 702 | cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) | 714 | cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) |
| 715 | klog — kernel boot log (cat /dev/klog; WRITING <dst> persists it) | ||
| 703 | hexdump — hex-dump a file or input stream | 716 | hexdump — hex-dump a file or input stream |
| 704 | mkdir — create a directory | 717 | mkdir — create a directory |
| 705 | touch — create an empty file | 718 | touch — create an empty file |
| @@ -789,6 +802,29 @@ fn cmd_ls(p: &Parsed) { | |||
| 789 | } | 802 | } |
| 790 | 803 | ||
| 791 | // --------------------------------------------------------------------------- | 804 | // --------------------------------------------------------------------------- |
| 805 | // klog — kernel log (thin alias for `cat /dev/klog`) | ||
| 806 | // --------------------------------------------------------------------------- | ||
| 807 | |||
| 808 | fn cmd_klog(p: &Parsed) { | ||
| 809 | if !p.args.is_empty() { | ||
| 810 | term_err("klog: usage: klog [-h N] [-t N] [-g PAT] [-p] [WRITING <dst> | TO <dst>]"); | ||
| 811 | return; | ||
| 812 | } | ||
| 813 | // Alias for `cat /dev/klog` — same filters and redirection, so | ||
| 814 | // `klog -t 5` shows the last 5 log lines and `klog WRITING | ||
| 815 | // /disk/.../boot.log` persists the boot log. Input keywords were | ||
| 816 | // already rejected (klog does not accept input). | ||
| 817 | let q = Parsed { | ||
| 818 | cmd: alloc::string::String::from("klog"), | ||
| 819 | args: alloc::vec!["/dev/klog".into()], | ||
| 820 | input: Input::None, | ||
| 821 | filters: p.filters.clone(), | ||
| 822 | output: p.output.clone(), | ||
| 823 | }; | ||
| 824 | cmd_cat(&q); | ||
| 825 | } | ||
| 826 | |||
| 827 | // --------------------------------------------------------------------------- | ||
| 792 | // cat — file and stream consumers | 828 | // cat — file and stream consumers |
| 793 | // --------------------------------------------------------------------------- | 829 | // --------------------------------------------------------------------------- |
| 794 | 830 | ||
| @@ -1471,8 +1507,7 @@ fn cmd_stress(p: &Parsed) { | |||
| 1471 | } | 1507 | } |
| 1472 | 1508 | ||
| 1473 | fn cmd_halt() -> ! { | 1509 | fn cmd_halt() -> ! { |
| 1474 | gop_println!("halting..."); | 1510 | term_println("halting..."); |
| 1475 | serial_println!("halting..."); | ||
| 1476 | rsnix::power::halt() | 1511 | rsnix::power::halt() |
| 1477 | } | 1512 | } |
| 1478 | 1513 | ||
| @@ -1515,7 +1550,6 @@ fn cmd_pci(p: &Parsed) { | |||
| 1515 | } | 1550 | } |
| 1516 | 1551 | ||
| 1517 | fn cmd_poweroff() -> ! { | 1552 | fn cmd_poweroff() -> ! { |
| 1518 | gop_println!("shutting down..."); | 1553 | term_println("shutting down..."); |
| 1519 | serial_println!("shutting down..."); | ||
| 1520 | rsnix::power::poweroff() | 1554 | rsnix::power::poweroff() |
| 1521 | } | 1555 | } |
