Next Generation WASM Microkernel Operating System
at jonas/refactor/mem 552 lines 16 kB view raw
1// Copyright 2025 Jonas Kruckenberg 2// 3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or 4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or 5// http://opensource.org/licenses/MIT>, at your option. This file may not be 6// copied, modified, or distributed except according to those terms. 7 8//! Basic kernel shell for debugging purposes, taken from 9//! <https://github.com/hawkw/mycelium/blob/main/src/shell.rs> (MIT) 10 11const S: &str = r#" 12 __ ___ ____ 13 / /__ |_ ||_ / 14 / '_// __/_/_ < 15/_/\_\/____/____/ 16"#; 17 18use alloc::string::{String, ToString}; 19use core::fmt; 20use core::fmt::Write; 21use core::ops::DerefMut; 22use core::range::Range; 23use core::str::FromStr; 24 25use fallible_iterator::FallibleIterator; 26use kasync::executor::Executor; 27use spin::{Barrier, OnceLock}; 28 29use crate::device_tree::DeviceTree; 30use crate::mem::{Mmap, PhysicalAddress, with_kernel_aspace}; 31use crate::state::global; 32use crate::{arch, irq}; 33 34static COMMANDS: &[Command] = &[PANIC, FAULT, VERSION, SHUTDOWN]; 35 36pub fn init(devtree: &'static DeviceTree, sched: &'static Executor, num_cpus: usize) { 37 // The `Barrier` below is here so that the maybe verbose startup logging is 38 // out of the way before dropping the user into the kernel shell. If we don't 39 // wait for the last CPU to have finished initializing it will mess up the shell output. 40 static SYNC: OnceLock<Barrier> = OnceLock::new(); 41 let barrier = SYNC.get_or_init(|| Barrier::new(num_cpus)); 42 43 if barrier.wait().is_leader() { 44 tracing::info!("{S}"); 45 tracing::info!("type `help` to list available commands"); 46 47 sched 48 .try_spawn(async move { 49 let (mut uart, _mmap, irq_num) = init_uart(devtree); 50 51 let mut line = String::new(); 52 loop { 53 let res = irq::next_event(irq_num).await; 54 assert!(res.is_ok()); 55 let mut newline = false; 56 57 let ch = uart.recv() as char; 58 uart.write_char(ch).unwrap(); 59 match ch { 60 '\n' | '\r' => { 61 newline = true; 62 uart.write_str("\n\r").unwrap(); 63 } 64 '\u{007F}' => { 65 line.pop(); 66 } 67 ch => line.push(ch), 68 } 69 70 if newline { 71 eval(&line); 72 line.clear(); 73 } 74 } 75 }) 76 .unwrap(); 77 } 78} 79 80fn init_uart(devtree: &DeviceTree) -> (uart_16550::SerialPort, Mmap, u32) { 81 let s = devtree.find_by_path("/soc/serial").unwrap(); 82 assert!(s.is_compatible(["ns16550a"])); 83 84 let clock_freq = s.property("clock-frequency").unwrap().as_u32().unwrap(); 85 let mut regs = s.regs().unwrap(); 86 let reg = regs.next().unwrap().unwrap(); 87 assert!(regs.next().unwrap().is_none()); 88 let irq_num = s.property("interrupts").unwrap().as_u32().unwrap(); 89 90 let mmap = with_kernel_aspace(|aspace| { 91 // FIXME: this is gross, we're using the PhysicalAddress as an alignment utility :/ 92 let size = PhysicalAddress::new(reg.size.unwrap()) 93 .checked_align_up(arch::PAGE_SIZE) 94 .unwrap() 95 .get(); 96 97 let range_phys = { 98 let start = PhysicalAddress::new(reg.starting_address); 99 Range::from(start..start.checked_add(size).unwrap()) 100 }; 101 102 let mmap = Mmap::new_phys( 103 aspace.clone(), 104 range_phys, 105 size, 106 arch::PAGE_SIZE, 107 Some("UART-16550".to_string()), 108 ) 109 .unwrap(); 110 111 mmap.commit(aspace.lock().deref_mut(), Range::from(0..size), true) 112 .unwrap(); 113 114 mmap 115 }); 116 117 // Safety: info comes from device tree 118 let uart = unsafe { uart_16550::SerialPort::new(mmap.range().start.get(), clock_freq, 115200) }; 119 120 (uart, mmap, irq_num) 121} 122 123pub fn eval(line: &str) { 124 if line == "help" { 125 tracing::info!(target: "shell", "available commands:"); 126 print_help("", COMMANDS); 127 tracing::info!(target: "shell", ""); 128 return; 129 } 130 131 match handle_command(Context::new(line), COMMANDS) { 132 Ok(_) => {} 133 Err(error) => tracing::error!(target: "shell", "error: {error}"), 134 } 135} 136 137const PANIC: Command = Command::new("panic") 138 .with_usage("<MESSAGE>") 139 .with_help("cause a kernel panic with the given message. use with caution.") 140 .with_fn(|line| { 141 panic!("{}", line.current); 142 }); 143 144const FAULT: Command = Command::new("fault") 145 .with_help("cause a CPU fault (null pointer dereference). use with caution.") 146 .with_fn(|_| { 147 // Safety: This actually *is* unsafe and *is* causing problematic behaviour, but that is exactly what 148 // we want here! 149 unsafe { 150 #[expect(clippy::zero_ptr, reason = "we actually want to cause a fault here")] 151 (0x0 as *const u8).read_volatile(); 152 } 153 Ok(()) 154 }); 155 156const VERSION: Command = Command::new("version") 157 .with_help("print verbose build and version info.") 158 .with_fn(|_| { 159 tracing::info!("k23 v{}", env!("CARGO_PKG_VERSION")); 160 tracing::info!(build.version = %concat!( 161 env!("CARGO_PKG_VERSION"), 162 "-", 163 env!("VERGEN_GIT_BRANCH"), 164 ".", 165 env!("VERGEN_GIT_SHA") 166 )); 167 tracing::info!(build.timestamp = %env!("VERGEN_BUILD_TIMESTAMP")); 168 tracing::info!(build.opt_level = %env!("VERGEN_CARGO_OPT_LEVEL")); 169 tracing::info!(build.target = %env!("VERGEN_CARGO_TARGET_TRIPLE")); 170 tracing::info!(commit.sha = %env!("VERGEN_GIT_SHA")); 171 tracing::info!(commit.branch = %env!("VERGEN_GIT_BRANCH")); 172 tracing::info!(commit.date = %env!("VERGEN_GIT_COMMIT_TIMESTAMP")); 173 tracing::info!(rustc.version = %env!("VERGEN_RUSTC_SEMVER")); 174 tracing::info!(rustc.channel = %env!("VERGEN_RUSTC_CHANNEL")); 175 176 Ok(()) 177 }); 178 179const SHUTDOWN: Command = Command::new("shutdown") 180 .with_help("exit the kernel and shutdown the machine.") 181 .with_fn(|_| { 182 tracing::info!("Bye, Bye!"); 183 184 global().executor.close(); 185 186 Ok(()) 187 }); 188 189#[derive(Debug)] 190pub struct Command<'cmd> { 191 name: &'cmd str, 192 help: &'cmd str, 193 usage: &'cmd str, 194 run: fn(Context<'_>) -> CmdResult<'_>, 195} 196 197pub type CmdResult<'a> = Result<(), Error<'a>>; 198 199#[derive(Debug)] 200pub struct Error<'a> { 201 line: &'a str, 202 kind: ErrorKind<'a>, 203} 204 205#[derive(Debug)] 206enum ErrorKind<'a> { 207 UnknownCommand(&'a [Command<'a>]), 208 InvalidArguments { 209 help: &'a str, 210 arg: &'a str, 211 flag: Option<&'a str>, 212 }, 213 FlagRequired { 214 flags: &'a [&'a str], 215 }, 216 Other(&'static str), 217} 218 219#[derive(Copy, Clone)] 220pub struct Context<'cmd> { 221 line: &'cmd str, 222 current: &'cmd str, 223} 224 225fn print_help(parent_cmd: &str, commands: &[Command]) { 226 let parent_cmd_pad = if parent_cmd.is_empty() { "" } else { " " }; 227 for command in commands { 228 tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}{command}"); 229 } 230 tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}help --- prints this help message"); 231} 232 233fn handle_command<'cmd>(ctx: Context<'cmd>, commands: &'cmd [Command]) -> CmdResult<'cmd> { 234 let chunk = ctx.current.trim(); 235 for cmd in commands { 236 if let Some(current) = chunk.strip_prefix(cmd.name) { 237 let current = current.trim(); 238 239 return panic_unwind2::catch_unwind(|| cmd.run(Context { current, ..ctx })).unwrap_or( 240 { 241 Err(Error { 242 line: cmd.name, 243 kind: ErrorKind::Other("command failed"), 244 }) 245 }, 246 ); 247 } 248 } 249 250 Err(ctx.unknown_command(commands)) 251} 252 253// === impl Command === 254 255impl<'cmd> Command<'cmd> { 256 #[must_use] 257 pub const fn new(name: &'cmd str) -> Self { 258 #[cold] 259 fn invalid_command(_ctx: Context<'_>) -> CmdResult<'_> { 260 panic!("command is missing run function, this is a bug"); 261 } 262 263 Self { 264 name, 265 help: "", 266 usage: "", 267 run: invalid_command, 268 } 269 } 270 271 #[must_use] 272 pub const fn with_help(self, help: &'cmd str) -> Self { 273 Self { help, ..self } 274 } 275 276 #[must_use] 277 pub const fn with_usage(self, usage: &'cmd str) -> Self { 278 Self { usage, ..self } 279 } 280 281 #[must_use] 282 pub const fn with_fn(self, run: fn(Context<'_>) -> CmdResult<'_>) -> Self { 283 Self { run, ..self } 284 } 285 286 pub fn run<'ctx>(&'cmd self, ctx: Context<'ctx>) -> CmdResult<'ctx> 287 where 288 'cmd: 'ctx, 289 { 290 let current = ctx.current.trim(); 291 292 if current == "help" { 293 let name = ctx.line.strip_suffix(" help").unwrap_or("<???BUG???>"); 294 tracing::info!(target: "shell", "{name}"); 295 296 return Ok(()); 297 } 298 299 (self.run)(ctx) 300 } 301} 302 303impl fmt::Display for Command<'_> { 304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 305 let Self { 306 run: _func, 307 name, 308 help, 309 usage, 310 } = self; 311 312 write!( 313 f, 314 "{name}{usage_pad}{usage} --- {help}", 315 usage_pad = if !usage.is_empty() { " " } else { "" }, 316 ) 317 } 318} 319 320// === impl Error === 321 322impl fmt::Display for Error<'_> { 323 fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result { 324 fn command_names<'cmd>( 325 cmds: &'cmd [Command<'cmd>], 326 ) -> impl Iterator<Item = &'cmd str> + 'cmd { 327 cmds.iter() 328 .map(|Command { name, .. }| *name) 329 .chain(core::iter::once("help")) 330 } 331 332 fn fmt_flag_names(f: &mut fmt::Formatter<'_>, flags: &[&str]) -> fmt::Result { 333 let mut names = flags.iter(); 334 if let Some(name) = names.next() { 335 f.write_str(name)?; 336 for name in names { 337 write!(f, "|{name}")?; 338 } 339 } 340 Ok(()) 341 } 342 343 let Self { line, kind } = self; 344 match kind { 345 ErrorKind::UnknownCommand(commands) => { 346 write!(f, "unknown command {line:?}, expected one of: [")?; 347 comma_delimited(&mut f, command_names(commands))?; 348 f.write_char(']')?; 349 } 350 ErrorKind::InvalidArguments { help, arg, flag } => { 351 f.write_str("invalid argument")?; 352 if let Some(flag) = flag { 353 write!(f, " {flag}")?; 354 } 355 write!(f, " {arg:?}: {help}")?; 356 } 357 ErrorKind::FlagRequired { flags } => { 358 write!(f, "the '{line}' command requires the ")?; 359 fmt_flag_names(f, flags)?; 360 write!(f, " flag")?; 361 } 362 ErrorKind::Other(msg) => write!(f, "could not execute {line:?}: {msg}")?, 363 } 364 365 Ok(()) 366 } 367} 368 369impl core::error::Error for Error<'_> {} 370 371fn comma_delimited<F: fmt::Display>( 372 mut writer: impl Write, 373 values: impl IntoIterator<Item = F>, 374) -> fmt::Result { 375 let mut values = values.into_iter(); 376 if let Some(value) = values.next() { 377 write!(writer, "{value}")?; 378 for value in values { 379 write!(writer, ", {value}")?; 380 } 381 } 382 383 Ok(()) 384} 385 386// === impl Context === 387 388impl<'cmd> Context<'cmd> { 389 pub const fn new(line: &'cmd str) -> Self { 390 Self { 391 line, 392 current: line, 393 } 394 } 395 396 pub fn command(&self) -> &'cmd str { 397 self.current.trim() 398 } 399 400 fn unknown_command(&self, commands: &'cmd [Command]) -> Error<'cmd> { 401 Error { 402 line: self.line, 403 kind: ErrorKind::UnknownCommand(commands), 404 } 405 } 406 407 pub fn invalid_argument(&self, help: &'static str) -> Error<'cmd> { 408 Error { 409 line: self.line, 410 kind: ErrorKind::InvalidArguments { 411 arg: self.current, 412 flag: None, 413 help, 414 }, 415 } 416 } 417 418 pub fn invalid_argument_named(&self, name: &'static str, help: &'static str) -> Error<'cmd> { 419 Error { 420 line: self.line, 421 kind: ErrorKind::InvalidArguments { 422 arg: self.current, 423 flag: Some(name), 424 help, 425 }, 426 } 427 } 428 429 pub fn other_error(&self, msg: &'static str) -> Error<'cmd> { 430 Error { 431 line: self.line, 432 kind: ErrorKind::Other(msg), 433 } 434 } 435 436 pub fn parse_bool_flag(&mut self, flag: &str) -> bool { 437 if let Some(rest) = self.command().trim().strip_prefix(flag) { 438 self.current = rest.trim(); 439 true 440 } else { 441 false 442 } 443 } 444 445 pub fn parse_optional_u32_hex_or_dec( 446 &mut self, 447 name: &'static str, 448 ) -> Result<Option<u32>, Error<'cmd>> { 449 let (chunk, rest) = match self.command().split_once(" ") { 450 Some((chunk, rest)) => (chunk.trim(), rest), 451 None => (self.command(), ""), 452 }; 453 454 if chunk.is_empty() { 455 return Ok(None); 456 } 457 458 let val = if let Some(hex_num) = chunk.strip_prefix("0x") { 459 u32::from_str_radix(hex_num.trim(), 16).map_err(|_| Error { 460 line: self.line, 461 kind: ErrorKind::InvalidArguments { 462 arg: chunk, 463 flag: Some(name), 464 help: "expected a 32-bit hex number", 465 }, 466 })? 467 } else { 468 u32::from_str(chunk).map_err(|_| Error { 469 line: self.line, 470 kind: ErrorKind::InvalidArguments { 471 arg: chunk, 472 flag: Some(name), 473 help: "expected a 32-bit decimal number", 474 }, 475 })? 476 }; 477 478 self.current = rest; 479 Ok(Some(val)) 480 } 481 482 pub fn parse_u32_hex_or_dec(&mut self, name: &'static str) -> Result<u32, Error<'cmd>> { 483 self.parse_optional_u32_hex_or_dec(name).and_then(|val| { 484 val.ok_or_else(|| self.invalid_argument_named(name, "expected a number")) 485 }) 486 } 487 488 pub fn parse_optional_flag<T>( 489 &mut self, 490 names: &'static [&'static str], 491 ) -> Result<Option<T>, Error<'cmd>> 492 where 493 T: FromStr, 494 T::Err: fmt::Display, 495 { 496 for name in names { 497 if let Some(rest) = self.command().strip_prefix(name) { 498 let (chunk, rest) = match rest.trim().split_once(" ") { 499 Some((chunk, rest)) => (chunk.trim(), rest), 500 None => (rest, ""), 501 }; 502 503 if chunk.is_empty() { 504 return Err(Error { 505 line: self.line, 506 kind: ErrorKind::InvalidArguments { 507 arg: chunk, 508 flag: Some(name), 509 help: "expected a value", 510 }, 511 }); 512 } 513 514 match chunk.parse() { 515 Ok(val) => { 516 self.current = rest; 517 return Ok(Some(val)); 518 } 519 Err(e) => { 520 tracing::warn!(target: "shell", "invalid value {chunk:?} for flag {name}: {e}"); 521 return Err(Error { 522 line: self.line, 523 kind: ErrorKind::InvalidArguments { 524 arg: chunk, 525 flag: Some(name), 526 help: "invalid value", 527 }, 528 }); 529 } 530 } 531 } 532 } 533 534 Ok(None) 535 } 536 537 pub fn parse_required_flag<T>( 538 &mut self, 539 names: &'static [&'static str], 540 ) -> Result<T, Error<'cmd>> 541 where 542 T: FromStr, 543 T::Err: fmt::Display, 544 { 545 self.parse_optional_flag(names).and_then(|val| { 546 val.ok_or(Error { 547 line: self.line, 548 kind: ErrorKind::FlagRequired { flags: names }, 549 }) 550 }) 551 } 552}