Next Generation WASM Microkernel Operating System
at main 546 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, Range}; 22use core::str::FromStr; 23 24use fallible_iterator::FallibleIterator; 25use k23_spin::{Barrier, OnceLock}; 26use kasync::executor::Executor; 27use kmem::{AddressRangeExt, PhysicalAddress}; 28 29use crate::device_tree::DeviceTree; 30use crate::mem::{Mmap, 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 .align_up(arch::PAGE_SIZE) 94 .get(); 95 96 let range_phys = Range::from_start_len(PhysicalAddress::new(reg.starting_address), size); 97 98 let mmap = Mmap::new_phys( 99 aspace.clone(), 100 range_phys, 101 size, 102 arch::PAGE_SIZE, 103 Some("UART-16550".to_string()), 104 ) 105 .unwrap(); 106 107 mmap.commit(aspace.lock().deref_mut(), 0..size, true) 108 .unwrap(); 109 110 mmap 111 }); 112 113 // Safety: info comes from device tree 114 let uart = unsafe { uart_16550::SerialPort::new(mmap.range().start.get(), clock_freq, 115200) }; 115 116 (uart, mmap, irq_num) 117} 118 119pub fn eval(line: &str) { 120 if line == "help" { 121 tracing::info!(target: "shell", "available commands:"); 122 print_help("", COMMANDS); 123 tracing::info!(target: "shell", ""); 124 return; 125 } 126 127 match handle_command(Context::new(line), COMMANDS) { 128 Ok(_) => {} 129 Err(error) => tracing::error!(target: "shell", "error: {error}"), 130 } 131} 132 133const PANIC: Command = Command::new("panic") 134 .with_usage("<MESSAGE>") 135 .with_help("cause a kernel panic with the given message. use with caution.") 136 .with_fn(|line| { 137 panic!("{}", line.current); 138 }); 139 140const FAULT: Command = Command::new("fault") 141 .with_help("cause a CPU fault (null pointer dereference). use with caution.") 142 .with_fn(|_| { 143 // Safety: This actually *is* unsafe and *is* causing problematic behaviour, but that is exactly what 144 // we want here! 145 unsafe { 146 core::ptr::dangling::<u8>().read_volatile(); 147 } 148 Ok(()) 149 }); 150 151const VERSION: Command = Command::new("version") 152 .with_help("print verbose build and version info.") 153 .with_fn(|_| { 154 tracing::info!("k23 v{}", env!("CARGO_PKG_VERSION")); 155 tracing::info!(build.version = %concat!( 156 env!("CARGO_PKG_VERSION"), 157 "-", 158 env!("VERGEN_GIT_BRANCH"), 159 ".", 160 env!("VERGEN_GIT_SHA") 161 )); 162 tracing::info!(build.timestamp = %env!("VERGEN_BUILD_TIMESTAMP")); 163 tracing::info!(build.opt_level = %env!("VERGEN_CARGO_OPT_LEVEL")); 164 tracing::info!(build.target = %env!("VERGEN_CARGO_TARGET_TRIPLE")); 165 tracing::info!(commit.sha = %env!("VERGEN_GIT_SHA")); 166 tracing::info!(commit.branch = %env!("VERGEN_GIT_BRANCH")); 167 tracing::info!(commit.date = %env!("VERGEN_GIT_COMMIT_TIMESTAMP")); 168 tracing::info!(rustc.version = %env!("VERGEN_RUSTC_SEMVER")); 169 tracing::info!(rustc.channel = %env!("VERGEN_RUSTC_CHANNEL")); 170 171 Ok(()) 172 }); 173 174const SHUTDOWN: Command = Command::new("shutdown") 175 .with_help("exit the kernel and shutdown the machine.") 176 .with_fn(|_| { 177 tracing::info!("Bye, Bye!"); 178 179 global().executor.close(); 180 181 Ok(()) 182 }); 183 184#[derive(Debug)] 185pub struct Command<'cmd> { 186 name: &'cmd str, 187 help: &'cmd str, 188 usage: &'cmd str, 189 run: fn(Context<'_>) -> CmdResult<'_>, 190} 191 192pub type CmdResult<'a> = Result<(), Error<'a>>; 193 194#[derive(Debug)] 195pub struct Error<'a> { 196 line: &'a str, 197 kind: ErrorKind<'a>, 198} 199 200#[derive(Debug)] 201enum ErrorKind<'a> { 202 UnknownCommand(&'a [Command<'a>]), 203 InvalidArguments { 204 help: &'a str, 205 arg: &'a str, 206 flag: Option<&'a str>, 207 }, 208 FlagRequired { 209 flags: &'a [&'a str], 210 }, 211 Other(&'static str), 212} 213 214#[derive(Copy, Clone)] 215pub struct Context<'cmd> { 216 line: &'cmd str, 217 current: &'cmd str, 218} 219 220fn print_help(parent_cmd: &str, commands: &[Command]) { 221 let parent_cmd_pad = if parent_cmd.is_empty() { "" } else { " " }; 222 for command in commands { 223 tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}{command}"); 224 } 225 tracing::info!(target: "shell", " {parent_cmd}{parent_cmd_pad}help --- prints this help message"); 226} 227 228fn handle_command<'cmd>(ctx: Context<'cmd>, commands: &'cmd [Command]) -> CmdResult<'cmd> { 229 let chunk = ctx.current.trim(); 230 for cmd in commands { 231 if let Some(current) = chunk.strip_prefix(cmd.name) { 232 let current = current.trim(); 233 234 return k23_panic_unwind::catch_unwind(|| cmd.run(Context { current, ..ctx })) 235 .unwrap_or({ 236 Err(Error { 237 line: cmd.name, 238 kind: ErrorKind::Other("command failed"), 239 }) 240 }); 241 } 242 } 243 244 Err(ctx.unknown_command(commands)) 245} 246 247// === impl Command === 248 249impl<'cmd> Command<'cmd> { 250 #[must_use] 251 pub const fn new(name: &'cmd str) -> Self { 252 #[cold] 253 fn invalid_command(_ctx: Context<'_>) -> CmdResult<'_> { 254 panic!("command is missing run function, this is a bug"); 255 } 256 257 Self { 258 name, 259 help: "", 260 usage: "", 261 run: invalid_command, 262 } 263 } 264 265 #[must_use] 266 pub const fn with_help(self, help: &'cmd str) -> Self { 267 Self { help, ..self } 268 } 269 270 #[must_use] 271 pub const fn with_usage(self, usage: &'cmd str) -> Self { 272 Self { usage, ..self } 273 } 274 275 #[must_use] 276 pub const fn with_fn(self, run: fn(Context<'_>) -> CmdResult<'_>) -> Self { 277 Self { run, ..self } 278 } 279 280 pub fn run<'ctx>(&'cmd self, ctx: Context<'ctx>) -> CmdResult<'ctx> 281 where 282 'cmd: 'ctx, 283 { 284 let current = ctx.current.trim(); 285 286 if current == "help" { 287 let name = ctx.line.strip_suffix(" help").unwrap_or("<???BUG???>"); 288 tracing::info!(target: "shell", "{name}"); 289 290 return Ok(()); 291 } 292 293 (self.run)(ctx) 294 } 295} 296 297impl fmt::Display for Command<'_> { 298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 299 let Self { 300 run: _func, 301 name, 302 help, 303 usage, 304 } = self; 305 306 write!( 307 f, 308 "{name}{usage_pad}{usage} --- {help}", 309 usage_pad = if !usage.is_empty() { " " } else { "" }, 310 ) 311 } 312} 313 314// === impl Error === 315 316impl fmt::Display for Error<'_> { 317 fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result { 318 fn command_names<'cmd>( 319 cmds: &'cmd [Command<'cmd>], 320 ) -> impl Iterator<Item = &'cmd str> + 'cmd { 321 cmds.iter() 322 .map(|Command { name, .. }| *name) 323 .chain(core::iter::once("help")) 324 } 325 326 fn fmt_flag_names(f: &mut fmt::Formatter<'_>, flags: &[&str]) -> fmt::Result { 327 let mut names = flags.iter(); 328 if let Some(name) = names.next() { 329 f.write_str(name)?; 330 for name in names { 331 write!(f, "|{name}")?; 332 } 333 } 334 Ok(()) 335 } 336 337 let Self { line, kind } = self; 338 match kind { 339 ErrorKind::UnknownCommand(commands) => { 340 write!(f, "unknown command {line:?}, expected one of: [")?; 341 comma_delimited(&mut f, command_names(commands))?; 342 f.write_char(']')?; 343 } 344 ErrorKind::InvalidArguments { help, arg, flag } => { 345 f.write_str("invalid argument")?; 346 if let Some(flag) = flag { 347 write!(f, " {flag}")?; 348 } 349 write!(f, " {arg:?}: {help}")?; 350 } 351 ErrorKind::FlagRequired { flags } => { 352 write!(f, "the '{line}' command requires the ")?; 353 fmt_flag_names(f, flags)?; 354 write!(f, " flag")?; 355 } 356 ErrorKind::Other(msg) => write!(f, "could not execute {line:?}: {msg}")?, 357 } 358 359 Ok(()) 360 } 361} 362 363impl core::error::Error for Error<'_> {} 364 365fn comma_delimited<F: fmt::Display>( 366 mut writer: impl Write, 367 values: impl IntoIterator<Item = F>, 368) -> fmt::Result { 369 let mut values = values.into_iter(); 370 if let Some(value) = values.next() { 371 write!(writer, "{value}")?; 372 for value in values { 373 write!(writer, ", {value}")?; 374 } 375 } 376 377 Ok(()) 378} 379 380// === impl Context === 381 382impl<'cmd> Context<'cmd> { 383 pub const fn new(line: &'cmd str) -> Self { 384 Self { 385 line, 386 current: line, 387 } 388 } 389 390 pub fn command(&self) -> &'cmd str { 391 self.current.trim() 392 } 393 394 fn unknown_command(&self, commands: &'cmd [Command]) -> Error<'cmd> { 395 Error { 396 line: self.line, 397 kind: ErrorKind::UnknownCommand(commands), 398 } 399 } 400 401 pub fn invalid_argument(&self, help: &'static str) -> Error<'cmd> { 402 Error { 403 line: self.line, 404 kind: ErrorKind::InvalidArguments { 405 arg: self.current, 406 flag: None, 407 help, 408 }, 409 } 410 } 411 412 pub fn invalid_argument_named(&self, name: &'static str, help: &'static str) -> Error<'cmd> { 413 Error { 414 line: self.line, 415 kind: ErrorKind::InvalidArguments { 416 arg: self.current, 417 flag: Some(name), 418 help, 419 }, 420 } 421 } 422 423 pub fn other_error(&self, msg: &'static str) -> Error<'cmd> { 424 Error { 425 line: self.line, 426 kind: ErrorKind::Other(msg), 427 } 428 } 429 430 pub fn parse_bool_flag(&mut self, flag: &str) -> bool { 431 if let Some(rest) = self.command().trim().strip_prefix(flag) { 432 self.current = rest.trim(); 433 true 434 } else { 435 false 436 } 437 } 438 439 pub fn parse_optional_u32_hex_or_dec( 440 &mut self, 441 name: &'static str, 442 ) -> Result<Option<u32>, Error<'cmd>> { 443 let (chunk, rest) = match self.command().split_once(" ") { 444 Some((chunk, rest)) => (chunk.trim(), rest), 445 None => (self.command(), ""), 446 }; 447 448 if chunk.is_empty() { 449 return Ok(None); 450 } 451 452 let val = if let Some(hex_num) = chunk.strip_prefix("0x") { 453 u32::from_str_radix(hex_num.trim(), 16).map_err(|_| Error { 454 line: self.line, 455 kind: ErrorKind::InvalidArguments { 456 arg: chunk, 457 flag: Some(name), 458 help: "expected a 32-bit hex number", 459 }, 460 })? 461 } else { 462 u32::from_str(chunk).map_err(|_| Error { 463 line: self.line, 464 kind: ErrorKind::InvalidArguments { 465 arg: chunk, 466 flag: Some(name), 467 help: "expected a 32-bit decimal number", 468 }, 469 })? 470 }; 471 472 self.current = rest; 473 Ok(Some(val)) 474 } 475 476 pub fn parse_u32_hex_or_dec(&mut self, name: &'static str) -> Result<u32, Error<'cmd>> { 477 self.parse_optional_u32_hex_or_dec(name).and_then(|val| { 478 val.ok_or_else(|| self.invalid_argument_named(name, "expected a number")) 479 }) 480 } 481 482 pub fn parse_optional_flag<T>( 483 &mut self, 484 names: &'static [&'static str], 485 ) -> Result<Option<T>, Error<'cmd>> 486 where 487 T: FromStr, 488 T::Err: fmt::Display, 489 { 490 for name in names { 491 if let Some(rest) = self.command().strip_prefix(name) { 492 let (chunk, rest) = match rest.trim().split_once(" ") { 493 Some((chunk, rest)) => (chunk.trim(), rest), 494 None => (rest, ""), 495 }; 496 497 if chunk.is_empty() { 498 return Err(Error { 499 line: self.line, 500 kind: ErrorKind::InvalidArguments { 501 arg: chunk, 502 flag: Some(name), 503 help: "expected a value", 504 }, 505 }); 506 } 507 508 match chunk.parse() { 509 Ok(val) => { 510 self.current = rest; 511 return Ok(Some(val)); 512 } 513 Err(e) => { 514 tracing::warn!(target: "shell", "invalid value {chunk:?} for flag {name}: {e}"); 515 return Err(Error { 516 line: self.line, 517 kind: ErrorKind::InvalidArguments { 518 arg: chunk, 519 flag: Some(name), 520 help: "invalid value", 521 }, 522 }); 523 } 524 } 525 } 526 } 527 528 Ok(None) 529 } 530 531 pub fn parse_required_flag<T>( 532 &mut self, 533 names: &'static [&'static str], 534 ) -> Result<T, Error<'cmd>> 535 where 536 T: FromStr, 537 T::Err: fmt::Display, 538 { 539 self.parse_optional_flag(names).and_then(|val| { 540 val.ok_or(Error { 541 line: self.line, 542 kind: ErrorKind::FlagRequired { flags: names }, 543 }) 544 }) 545 } 546}