diff options
Diffstat (limited to 'src/shell.rs')
| -rw-r--r-- | src/shell.rs | 275 |
1 files changed, 0 insertions, 275 deletions
diff --git a/src/shell.rs b/src/shell.rs deleted file mode 100644 index 8e4cb62..0000000 --- a/src/shell.rs +++ /dev/null | |||
| @@ -1,275 +0,0 @@ | |||
| 1 | /// Minimal command shell — reads from PS/2 keyboard, writes to GOP + serial. | ||
| 2 | use crate::ps2::{self, KeyEvent}; | ||
| 3 | use crate::{gop_print, gop_println, serial_print, serial_println}; | ||
| 4 | |||
| 5 | /// Maximum input line length. | ||
| 6 | const LINE_BUF_SIZE: usize = 256; | ||
| 7 | |||
| 8 | /// Run the shell forever. | ||
| 9 | pub fn run() -> ! { | ||
| 10 | gop_println!("raisernics v{} — type 'help' for commands", env!("CARGO_PKG_VERSION")); | ||
| 11 | serial_println!("========== raisernics v{} ==========", env!("CARGO_PKG_VERSION")); | ||
| 12 | |||
| 13 | loop { | ||
| 14 | // Print prompt | ||
| 15 | gop_print!("> "); | ||
| 16 | serial_print!("> "); | ||
| 17 | |||
| 18 | // Read one line from keyboard | ||
| 19 | let line = read_line(); | ||
| 20 | |||
| 21 | // Parse and execute | ||
| 22 | let trimmed = line.trim(); | ||
| 23 | if trimmed.is_empty() { | ||
| 24 | continue; | ||
| 25 | } | ||
| 26 | |||
| 27 | let (cmd, args) = split_first_word(trimmed); | ||
| 28 | |||
| 29 | match cmd { | ||
| 30 | "help" => cmd_help(), | ||
| 31 | "echo" => cmd_echo(args), | ||
| 32 | "clear" => cmd_clear(), | ||
| 33 | "halt" | "poweroff" | "exit" => cmd_halt(), | ||
| 34 | "hello" => { | ||
| 35 | gop_println!("World!"); | ||
| 36 | serial_println!("World!"); | ||
| 37 | } | ||
| 38 | "info" => cmd_info(), | ||
| 39 | "mem" => cmd_mem(), | ||
| 40 | _ => { | ||
| 41 | gop_println!("unknown command: {}", cmd); | ||
| 42 | serial_println!("unknown command: {}", cmd); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | // --------------------------------------------------------------------------- | ||
| 49 | // Line input (PS/2 keyboard, no heap) | ||
| 50 | // --------------------------------------------------------------------------- | ||
| 51 | fn read_line() -> heapless_str::String<LINE_BUF_SIZE> { | ||
| 52 | let mut buf = heapless_str::String::<LINE_BUF_SIZE>::new(); | ||
| 53 | loop { | ||
| 54 | match ps2::poll_key() { | ||
| 55 | KeyEvent::Enter => { | ||
| 56 | crate::gop::put_char('\n'); | ||
| 57 | crate::serial::write_str("\r\n"); | ||
| 58 | return buf; | ||
| 59 | } | ||
| 60 | KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => { | ||
| 61 | if buf.push(c).is_ok() { | ||
| 62 | crate::gop::put_char(c); | ||
| 63 | // Echo to serial too | ||
| 64 | let mut s = [0u8; 4]; | ||
| 65 | let s = c.encode_utf8(&mut s); | ||
| 66 | crate::serial::write_str(s); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | KeyEvent::Backspace => { | ||
| 70 | if buf.pop().is_some() { | ||
| 71 | // GOP backspace: move cursor back and erase | ||
| 72 | crate::gop::put_char('\x08'); | ||
| 73 | // Serial backspace: send BS SP BS (standard terminal protocol) | ||
| 74 | crate::serial::write_str("\x08 \x08"); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | KeyEvent::Tab => { | ||
| 78 | for _ in 0..4 { | ||
| 79 | if buf.push(' ').is_ok() { | ||
| 80 | crate::gop::put_char(' '); | ||
| 81 | crate::serial::write_str(" "); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | _ => {} | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Split a command line into the first word and the rest. | ||
| 91 | fn split_first_word(s: &str) -> (&str, &str) { | ||
| 92 | let s = s.trim_start(); | ||
| 93 | if let Some(i) = s.find(char::is_whitespace) { | ||
| 94 | (&s[..i], s[i..].trim_start()) | ||
| 95 | } else { | ||
| 96 | (s, "") | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | // --------------------------------------------------------------------------- | ||
| 101 | // Built-in commands | ||
| 102 | // --------------------------------------------------------------------------- | ||
| 103 | |||
| 104 | fn cmd_help() { | ||
| 105 | gop_println!("available commands:"); | ||
| 106 | gop_println!(" help — show this message"); | ||
| 107 | gop_println!(" echo — echo arguments"); | ||
| 108 | gop_println!(" clear — clear screen"); | ||
| 109 | gop_println!(" hello — print 'World!'"); | ||
| 110 | gop_println!(" info — show system info"); | ||
| 111 | gop_println!(" mem — show memory info"); | ||
| 112 | gop_println!(" halt — halt the system"); | ||
| 113 | gop_println!(" exit — alias for halt"); | ||
| 114 | serial_println!("available commands:"); | ||
| 115 | serial_println!(" help, echo, clear, hello, info, mem, halt, exit"); | ||
| 116 | } | ||
| 117 | |||
| 118 | fn cmd_echo(args: &str) { | ||
| 119 | gop_println!("{}", args); | ||
| 120 | } | ||
| 121 | |||
| 122 | fn cmd_clear() { | ||
| 123 | crate::gop::clear_screen(); | ||
| 124 | } | ||
| 125 | |||
| 126 | fn cmd_info() { | ||
| 127 | // Test that the global allocator works: build a String on the heap | ||
| 128 | let mut s = alloc::string::String::from("raisernics v"); | ||
| 129 | s.push_str(env!("CARGO_PKG_VERSION")); | ||
| 130 | s.push_str(" — RNG custom kernel"); | ||
| 131 | gop_println!("{}", s); | ||
| 132 | gop_println!("arch: x86_64, freestanding, no userspace"); | ||
| 133 | serial_println!("{}", s); | ||
| 134 | serial_println!("arch: x86_64, freestanding, no userspace"); | ||
| 135 | // String is freed (dropped) when this scope ends — tests dealloc too | ||
| 136 | } | ||
| 137 | |||
| 138 | fn cmd_mem() { | ||
| 139 | let (map, key) = crate::memory::get_map(); | ||
| 140 | |||
| 141 | let mut total_kb = 0u64; | ||
| 142 | let mut conventional_kb = 0u64; | ||
| 143 | let mut loader_kb = 0u64; | ||
| 144 | let mut boot_code_kb = 0u64; | ||
| 145 | let mut boot_data_kb = 0u64; | ||
| 146 | let mut reserved_kb = 0u64; | ||
| 147 | let mut acpi_kb = 0u64; | ||
| 148 | let mut mmio_kb = 0u64; | ||
| 149 | |||
| 150 | for desc in map { | ||
| 151 | let pages = desc.number_of_pages; | ||
| 152 | let bytes_4k = pages * 4; | ||
| 153 | total_kb += bytes_4k; | ||
| 154 | match desc.kind { | ||
| 155 | 1 => reserved_kb += bytes_4k, | ||
| 156 | 3 => boot_code_kb += bytes_4k, | ||
| 157 | 4 => boot_data_kb += bytes_4k, | ||
| 158 | 5 | 10 => acpi_kb += bytes_4k, | ||
| 159 | 6 => loader_kb += bytes_4k, | ||
| 160 | 7 => conventional_kb += bytes_4k, | ||
| 161 | 11 | 12 => mmio_kb += bytes_4k, | ||
| 162 | _ => reserved_kb += bytes_4k, | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | // Build a big heap string with format! to test the allocator | ||
| 167 | let mut out = alloc::string::String::new(); | ||
| 168 | use alloc::fmt::Write; | ||
| 169 | let _ = writeln!(out, "memory map (key=0x{:x}, {} entries)", key, map.len()); | ||
| 170 | let _ = writeln!( | ||
| 171 | out, | ||
| 172 | " usable {:4} MB Conventional", | ||
| 173 | conventional_kb / 1024 | ||
| 174 | ); | ||
| 175 | let _ = writeln!( | ||
| 176 | out, | ||
| 177 | " boot code {:4} MB BootServicesCode", | ||
| 178 | boot_code_kb / 1024 | ||
| 179 | ); | ||
| 180 | let _ = writeln!( | ||
| 181 | out, | ||
| 182 | " boot data {:4} MB BootServicesData", | ||
| 183 | boot_data_kb / 1024 | ||
| 184 | ); | ||
| 185 | let _ = writeln!(out, " loader {:4} MB LoaderData", loader_kb / 1024); | ||
| 186 | let _ = writeln!(out, " ACPI {:4} MB", acpi_kb / 1024); | ||
| 187 | let _ = writeln!(out, " MMIO {:4} MB", mmio_kb / 1024); | ||
| 188 | let _ = writeln!(out, " reserved {:4} MB", reserved_kb / 1024); | ||
| 189 | let _ = writeln!( | ||
| 190 | out, | ||
| 191 | " raw total {:4} MB (incl. PCIe MMIO)", | ||
| 192 | total_kb / 1024 | ||
| 193 | ); | ||
| 194 | |||
| 195 | gop_print!("{}", out); | ||
| 196 | serial_print!("{}", out); | ||
| 197 | } | ||
| 198 | |||
| 199 | fn cmd_halt() -> ! { | ||
| 200 | gop_println!("halting..."); | ||
| 201 | serial_println!("halting..."); | ||
| 202 | loop { | ||
| 203 | unsafe { | ||
| 204 | core::arch::asm!("cli; hlt"); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | // --------------------------------------------------------------------------- | ||
| 210 | // Minimal fixed-capacity string for no-stack use (avoids alloc dependency) | ||
| 211 | // --------------------------------------------------------------------------- | ||
| 212 | mod heapless_str { | ||
| 213 | use core::fmt; | ||
| 214 | |||
| 215 | /// A fixed-capacity string allocated entirely on the stack. | ||
| 216 | pub struct String<const N: usize> { | ||
| 217 | buf: [u8; N], | ||
| 218 | len: usize, | ||
| 219 | } | ||
| 220 | |||
| 221 | impl<const N: usize> String<N> { | ||
| 222 | pub const fn new() -> Self { | ||
| 223 | String { | ||
| 224 | buf: [0u8; N], | ||
| 225 | len: 0, | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | pub fn as_str(&self) -> &str { | ||
| 230 | // SAFETY: we only write valid ASCII bytes. | ||
| 231 | unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) } | ||
| 232 | } | ||
| 233 | |||
| 234 | pub fn push(&mut self, c: char) -> Result<(), ()> { | ||
| 235 | if self.len >= N { | ||
| 236 | return Err(()); | ||
| 237 | } | ||
| 238 | self.buf[self.len] = c as u8; | ||
| 239 | self.len += 1; | ||
| 240 | Ok(()) | ||
| 241 | } | ||
| 242 | |||
| 243 | pub fn pop(&mut self) -> Option<char> { | ||
| 244 | if self.len == 0 { | ||
| 245 | return None; | ||
| 246 | } | ||
| 247 | self.len -= 1; | ||
| 248 | Some(self.buf[self.len] as char) | ||
| 249 | } | ||
| 250 | |||
| 251 | pub fn is_empty(&self) -> bool { | ||
| 252 | self.len == 0 | ||
| 253 | } | ||
| 254 | |||
| 255 | pub fn clear(&mut self) { | ||
| 256 | self.len = 0; | ||
| 257 | } | ||
| 258 | |||
| 259 | pub fn trim(&self) -> &str { | ||
| 260 | self.as_str().trim() | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | impl<const N: usize> fmt::Display for String<N> { | ||
| 265 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 266 | write!(f, "{}", self.as_str()) | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | impl<const N: usize> fmt::Debug for String<N> { | ||
| 271 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 272 | write!(f, "{:?}", self.as_str()) | ||
| 273 | } | ||
| 274 | } | ||
| 275 | } | ||
