summaryrefslogtreecommitdiffstats
path: root/src/shell.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/shell.rs')
-rw-r--r--src/shell.rs275
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.
2use crate::ps2::{self, KeyEvent};
3use crate::{gop_print, gop_println, serial_print, serial_println};
4
5/// Maximum input line length.
6const LINE_BUF_SIZE: usize = 256;
7
8/// Run the shell forever.
9pub 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// ---------------------------------------------------------------------------
51fn 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.
91fn 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
104fn 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
118fn cmd_echo(args: &str) {
119 gop_println!("{}", args);
120}
121
122fn cmd_clear() {
123 crate::gop::clear_screen();
124}
125
126fn 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
138fn 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
199fn 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// ---------------------------------------------------------------------------
212mod 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}