summaryrefslogtreecommitdiffstats
path: root/src/shell.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/shell.rs274
1 files changed, 274 insertions, 0 deletions
diff --git a/src/shell.rs b/src/shell.rs
new file mode 100644
index 0000000..f21525e
--- /dev/null
+++ b/src/shell.rs
@@ -0,0 +1,274 @@
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 v0.1 — type 'help' for commands");
11 serial_println!("========== raisernics v0.1 ==========");
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 v0.1");
129 s.push_str(" — RNG custom kernel");
130 gop_println!("{}", s);
131 gop_println!("arch: x86_64, freestanding, no userspace");
132 serial_println!("{}", s);
133 serial_println!("arch: x86_64, freestanding, no userspace");
134 // String is freed (dropped) when this scope ends — tests dealloc too
135}
136
137fn cmd_mem() {
138 let (map, key) = crate::memory::get_map();
139
140 let mut total_kb = 0u64;
141 let mut conventional_kb = 0u64;
142 let mut loader_kb = 0u64;
143 let mut boot_code_kb = 0u64;
144 let mut boot_data_kb = 0u64;
145 let mut reserved_kb = 0u64;
146 let mut acpi_kb = 0u64;
147 let mut mmio_kb = 0u64;
148
149 for desc in map {
150 let pages = desc.number_of_pages;
151 let bytes_4k = pages * 4;
152 total_kb += bytes_4k;
153 match desc.kind {
154 1 => reserved_kb += bytes_4k,
155 3 => boot_code_kb += bytes_4k,
156 4 => boot_data_kb += bytes_4k,
157 5 | 10 => acpi_kb += bytes_4k,
158 6 => loader_kb += bytes_4k,
159 7 => conventional_kb += bytes_4k,
160 11 | 12 => mmio_kb += bytes_4k,
161 _ => reserved_kb += bytes_4k,
162 }
163 }
164
165 // Build a big heap string with format! to test the allocator
166 let mut out = alloc::string::String::new();
167 use alloc::fmt::Write;
168 let _ = writeln!(out, "memory map (key=0x{:x}, {} entries)", key, map.len());
169 let _ = writeln!(
170 out,
171 " usable {:4} MB Conventional",
172 conventional_kb / 1024
173 );
174 let _ = writeln!(
175 out,
176 " boot code {:4} MB BootServicesCode",
177 boot_code_kb / 1024
178 );
179 let _ = writeln!(
180 out,
181 " boot data {:4} MB BootServicesData",
182 boot_data_kb / 1024
183 );
184 let _ = writeln!(out, " loader {:4} MB LoaderData", loader_kb / 1024);
185 let _ = writeln!(out, " ACPI {:4} MB", acpi_kb / 1024);
186 let _ = writeln!(out, " MMIO {:4} MB", mmio_kb / 1024);
187 let _ = writeln!(out, " reserved {:4} MB", reserved_kb / 1024);
188 let _ = writeln!(
189 out,
190 " raw total {:4} MB (incl. PCIe MMIO)",
191 total_kb / 1024
192 );
193
194 gop_print!("{}", out);
195 serial_print!("{}", out);
196}
197
198fn cmd_halt() -> ! {
199 gop_println!("halting...");
200 serial_println!("halting...");
201 loop {
202 unsafe {
203 core::arch::asm!("cli; hlt");
204 }
205 }
206}
207
208// ---------------------------------------------------------------------------
209// Minimal fixed-capacity string for no-stack use (avoids alloc dependency)
210// ---------------------------------------------------------------------------
211mod heapless_str {
212 use core::fmt;
213
214 /// A fixed-capacity string allocated entirely on the stack.
215 pub struct String<const N: usize> {
216 buf: [u8; N],
217 len: usize,
218 }
219
220 impl<const N: usize> String<N> {
221 pub const fn new() -> Self {
222 String {
223 buf: [0u8; N],
224 len: 0,
225 }
226 }
227
228 pub fn as_str(&self) -> &str {
229 // SAFETY: we only write valid ASCII bytes.
230 unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) }
231 }
232
233 pub fn push(&mut self, c: char) -> Result<(), ()> {
234 if self.len >= N {
235 return Err(());
236 }
237 self.buf[self.len] = c as u8;
238 self.len += 1;
239 Ok(())
240 }
241
242 pub fn pop(&mut self) -> Option<char> {
243 if self.len == 0 {
244 return None;
245 }
246 self.len -= 1;
247 Some(self.buf[self.len] as char)
248 }
249
250 pub fn is_empty(&self) -> bool {
251 self.len == 0
252 }
253
254 pub fn clear(&mut self) {
255 self.len = 0;
256 }
257
258 pub fn trim(&self) -> &str {
259 self.as_str().trim()
260 }
261 }
262
263 impl<const N: usize> fmt::Display for String<N> {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(f, "{}", self.as_str())
266 }
267 }
268
269 impl<const N: usize> fmt::Debug for String<N> {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 write!(f, "{:?}", self.as_str())
272 }
273 }
274}