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