diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 492c3aea..e17fb82d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -16,10 +16,8 @@ cfg-if.workspace = true cpumask.workspace = true kernel_guard.workspace = true kspin = "0.1" -lazy_static = {version = "1.5", default-features = false, features = ["spin_no_std"]} lazyinit = "0.2" log = "0.4" -spin = "0.9" timer_list = "0.1.0" toml = {version = "0.9", default-features = false} @@ -29,7 +27,7 @@ axstd.workspace = true # System dependent modules provided by ArceOS-Hypervisor. axhvc.workspace = true axvm.workspace = true -axvmconfig = {workspace = true} +axvmconfig = { workspace = true } # System independent crates provided by ArceOS, these crates could be imported by remote url. axerrno.workspace = true @@ -37,6 +35,12 @@ byte-unit = {version = "5", default-features = false, features = ["byte"]} extern-trait = "0.2" memory_addr.workspace = true driver.workspace = true +spin = "0.9" + +# axvm = { path = "../axvm" } +# axvmconfig = { version = "0.1", default-features = false } + +lazy_static = {version = "1.5", default-features = false, features = ["spin_no_std"]} [build-dependencies] anyhow = "1.0" diff --git a/kernel/src/main.rs b/kernel/src/main.rs index a6396ec1..e8b65bea 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -8,15 +8,18 @@ extern crate log; extern crate alloc; extern crate axstd as std; +extern crate driver; // extern crate axruntime; -extern crate driver; mod logo; -// mod shell; mod task; +mod shell; mod vmm; +pub use shell::*; +pub use vmm::*; + #[unsafe(no_mangle)] fn main() { logo::print_logo(); @@ -30,5 +33,5 @@ fn main() { info!("[OK] Default guest initialized"); vmm::wait_for_all_vms_exit(); info!("All guest VMs exited."); - // shell::console_init(); + shell::console_init(); } diff --git a/kernel/src/shell/command/mod.rs b/kernel/src/shell/command/mod.rs deleted file mode 100644 index 954a20bf..00000000 --- a/kernel/src/shell/command/mod.rs +++ /dev/null @@ -1,566 +0,0 @@ -mod base; -mod history; -mod vm; - -pub use base::*; -pub use history::*; -pub use vm::*; - -use std::io::prelude::*; -use std::string::String; -use std::vec::Vec; -use std::{collections::BTreeMap, string::ToString}; -use std::{print, println}; - -lazy_static::lazy_static! { - pub static ref COMMAND_TREE: BTreeMap = build_command_tree(); -} - -#[derive(Debug, Clone)] -pub struct CommandNode { - handler: Option, - subcommands: BTreeMap, - description: &'static str, - usage: Option<&'static str>, - #[allow(dead_code)] - log_level: log::LevelFilter, - options: Vec, - flags: Vec, -} - -#[derive(Debug, Clone)] -pub struct OptionDef { - name: &'static str, - short: Option, - long: Option<&'static str>, - description: &'static str, - required: bool, -} - -#[derive(Debug, Clone)] -pub struct FlagDef { - name: &'static str, - short: Option, - long: Option<&'static str>, - description: &'static str, -} - -#[derive(Debug, Clone)] -pub struct ParsedCommand { - pub command_path: Vec, - pub options: BTreeMap, - pub flags: BTreeMap, - pub positional_args: Vec, -} - -#[derive(Debug)] -pub enum ParseError { - UnknownCommand(String), - UnknownOption(String), - MissingValue(String), - MissingRequiredOption(String), - NoHandler(String), -} - -impl CommandNode { - pub fn new(description: &'static str) -> Self { - Self { - handler: None, - subcommands: BTreeMap::new(), - description, - usage: None, - log_level: log::LevelFilter::Off, - options: Vec::new(), - flags: Vec::new(), - } - } - - pub fn with_handler(mut self, handler: fn(&ParsedCommand)) -> Self { - self.handler = Some(handler); - self - } - - pub fn with_usage(mut self, usage: &'static str) -> Self { - self.usage = Some(usage); - self - } - - #[allow(dead_code)] - pub fn with_log_level(mut self, level: log::LevelFilter) -> Self { - self.log_level = level; - self - } - - pub fn with_option(mut self, option: OptionDef) -> Self { - self.options.push(option); - self - } - - pub fn with_flag(mut self, flag: FlagDef) -> Self { - self.flags.push(flag); - self - } - - pub fn add_subcommand>(mut self, name: S, node: CommandNode) -> Self { - self.subcommands.insert(name.into(), node); - self - } -} - -impl OptionDef { - pub fn new(name: &'static str, description: &'static str) -> Self { - Self { - name, - short: None, - long: None, - description, - required: false, - } - } - - #[allow(dead_code)] - pub fn with_short(mut self, short: char) -> Self { - self.short = Some(short); - self - } - - pub fn with_long(mut self, long: &'static str) -> Self { - self.long = Some(long); - self - } - - #[allow(dead_code)] - pub fn required(mut self) -> Self { - self.required = true; - self - } -} - -impl FlagDef { - pub fn new(name: &'static str, description: &'static str) -> Self { - Self { - name, - short: None, - long: None, - description, - } - } - - pub fn with_short(mut self, short: char) -> Self { - self.short = Some(short); - self - } - - pub fn with_long(mut self, long: &'static str) -> Self { - self.long = Some(long); - self - } -} - -// Command Parser -pub struct CommandParser; - -impl CommandParser { - pub fn parse(input: &str) -> Result { - let tokens = Self::tokenize(input); - if tokens.is_empty() { - return Err(ParseError::UnknownCommand("empty command".to_string())); - } - - // Find the command path - let (command_path, command_node, remaining_tokens) = Self::find_command(&tokens)?; - - // Parse the arguments - let (options, flags, positional_args) = Self::parse_args(remaining_tokens, command_node)?; - - // Validate required options - Self::validate_required_options(command_node, &options)?; - - Ok(ParsedCommand { - command_path, - options, - flags, - positional_args, - }) - } - - fn tokenize(input: &str) -> Vec { - let mut tokens = Vec::new(); - let mut current_token = String::new(); - let mut in_quotes = false; - let mut escape_next = false; - - for ch in input.chars() { - if escape_next { - current_token.push(ch); - escape_next = false; - } else if ch == '\\' { - escape_next = true; - } else if ch == '"' { - in_quotes = !in_quotes; - } else if ch.is_whitespace() && !in_quotes { - if !current_token.is_empty() { - tokens.push(current_token.clone()); - current_token.clear(); - } - } else { - current_token.push(ch); - } - } - - if !current_token.is_empty() { - tokens.push(current_token); - } - - tokens - } - - fn find_command( - tokens: &[String], - ) -> Result<(Vec, &CommandNode, &[String]), ParseError> { - let mut current_node = COMMAND_TREE - .get(&tokens[0]) - .ok_or_else(|| ParseError::UnknownCommand(tokens[0].clone()))?; - - let mut command_path = vec![tokens[0].clone()]; - let mut token_index = 1; - - // Traverse to find the deepest command node - while token_index < tokens.len() { - if let Some(subcommand) = current_node.subcommands.get(&tokens[token_index]) { - current_node = subcommand; - command_path.push(tokens[token_index].clone()); - token_index += 1; - } else { - break; - } - } - - Ok((command_path, current_node, &tokens[token_index..])) - } - - #[allow(clippy::type_complexity)] - fn parse_args( - tokens: &[String], - command_node: &CommandNode, - ) -> Result< - ( - BTreeMap, - BTreeMap, - Vec, - ), - ParseError, - > { - let mut options = BTreeMap::new(); - let mut flags = BTreeMap::new(); - let mut positional_args = Vec::new(); - let mut i = 0; - - while i < tokens.len() { - let token = &tokens[i]; - - if let Some(name) = token.strip_prefix("--") { - // Long options/flags - if let Some(eq_pos) = name.find('=') { - // --option=value format - let (opt_name, value) = name.split_at(eq_pos); - let value = &value[1..]; // Skip '=' - if Self::is_option(opt_name, command_node) { - options.insert(opt_name.to_string(), value.to_string()); - } else { - return Err(ParseError::UnknownOption(format!("--{opt_name}"))); - } - } else if Self::is_flag(name, command_node) { - flags.insert(name.to_string(), true); - } else if Self::is_option(name, command_node) { - // --option value format - if i + 1 >= tokens.len() { - return Err(ParseError::MissingValue(format!("--{name}"))); - } - options.insert(name.to_string(), tokens[i + 1].clone()); - i += 1; // Skip value - } else { - return Err(ParseError::UnknownOption(format!("--{name}"))); - } - } else if token.starts_with('-') && token.len() > 1 { - // Short options/flags - let chars: Vec = token[1..].chars().collect(); - for (j, &ch) in chars.iter().enumerate() { - if Self::is_short_flag(ch, command_node) { - flags.insert( - Self::get_flag_name_by_short(ch, command_node) - .unwrap() - .to_string(), - true, - ); - } else if Self::is_short_option(ch, command_node) { - let opt_name = Self::get_option_name_by_short(ch, command_node).unwrap(); - if j == chars.len() - 1 && i + 1 < tokens.len() { - // Last character and there is a next token as value - options.insert(opt_name.to_string(), tokens[i + 1].clone()); - i += 1; // Skip value - } else { - return Err(ParseError::MissingValue(format!("-{ch}"))); - } - } else { - return Err(ParseError::UnknownOption(format!("-{ch}"))); - } - } - } else { - // Positional arguments - positional_args.push(token.clone()); - } - i += 1; - } - - Ok((options, flags, positional_args)) - } - - fn is_option(name: &str, node: &CommandNode) -> bool { - node.options - .iter() - .any(|opt| (opt.long == Some(name)) || opt.name == name) - } - - fn is_flag(name: &str, node: &CommandNode) -> bool { - node.flags - .iter() - .any(|flag| (flag.long == Some(name)) || flag.name == name) - } - - fn is_short_option(ch: char, node: &CommandNode) -> bool { - node.options.iter().any(|opt| opt.short == Some(ch)) - } - - fn is_short_flag(ch: char, node: &CommandNode) -> bool { - node.flags.iter().any(|flag| flag.short == Some(ch)) - } - - fn get_option_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { - node.options - .iter() - .find(|opt| opt.short == Some(ch)) - .map(|opt| opt.name) - } - - fn get_flag_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { - node.flags - .iter() - .find(|flag| flag.short == Some(ch)) - .map(|flag| flag.name) - } - - fn validate_required_options( - node: &CommandNode, - options: &BTreeMap, - ) -> Result<(), ParseError> { - for option in &node.options { - if option.required && !options.contains_key(option.name) { - return Err(ParseError::MissingRequiredOption(option.name.to_string())); - } - } - Ok(()) - } -} - -// Command execution function -pub fn execute_command(input: &str) -> Result<(), ParseError> { - let parsed = CommandParser::parse(input)?; - - // Find the corresponding command node - let mut current_node = COMMAND_TREE.get(&parsed.command_path[0]).unwrap(); - for cmd in &parsed.command_path[1..] { - current_node = current_node.subcommands.get(cmd).unwrap(); - } - - // Execute the command - if let Some(handler) = current_node.handler { - handler(&parsed); - Ok(()) - } else { - Err(ParseError::NoHandler(parsed.command_path.join(" "))) - } -} - -// Build command tree -fn build_command_tree() -> BTreeMap { - let mut tree = BTreeMap::new(); - - build_base_cmd(&mut tree); - build_vm_cmd(&mut tree); - - tree -} - -// Helper function: Display command help -pub fn show_help(command_path: &[String]) -> Result<(), ParseError> { - let mut current_node = COMMAND_TREE - .get(&command_path[0]) - .ok_or_else(|| ParseError::UnknownCommand(command_path[0].clone()))?; - - for cmd in &command_path[1..] { - current_node = current_node - .subcommands - .get(cmd) - .ok_or_else(|| ParseError::UnknownCommand(cmd.clone()))?; - } - - println!("Command: {}", command_path.join(" ")); - println!("Description: {}", current_node.description); - - if let Some(usage) = current_node.usage { - println!("Usage: {}", usage); - } - - if !current_node.options.is_empty() { - println!("\nOptions:"); - for option in ¤t_node.options { - let mut opt_str = String::new(); - if let Some(short) = option.short { - opt_str.push_str(&format!("-{short}")); - } - if let Some(long) = option.long { - if !opt_str.is_empty() { - opt_str.push_str(", "); - } - opt_str.push_str(&format!("--{long}")); - } - if opt_str.is_empty() { - opt_str = option.name.to_string(); - } - - let required_str = if option.required { " (required)" } else { "" }; - println!(" {:<20} {}{}", opt_str, option.description, required_str); - } - } - - if !current_node.flags.is_empty() { - println!("\nFlags:"); - for flag in ¤t_node.flags { - let mut flag_str = String::new(); - if let Some(short) = flag.short { - flag_str.push_str(&format!("-{short}")); - } - if let Some(long) = flag.long { - if !flag_str.is_empty() { - flag_str.push_str(", "); - } - flag_str.push_str(&format!("--{long}")); - } - if flag_str.is_empty() { - flag_str = flag.name.to_string(); - } - - println!(" {:<20} {}", flag_str, flag.description); - } - } - - if !current_node.subcommands.is_empty() { - println!("\nSubcommands:"); - for (name, node) in ¤t_node.subcommands { - println!(" {:<20} {}", name, node.description); - } - } - - Ok(()) -} - -pub fn print_prompt() { - #[cfg(feature = "fs")] - print!("axvisor:{}$ ", std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - print!("axvisor:$ "); - std::io::stdout().flush().unwrap(); -} - -pub fn run_cmd_bytes(cmd_bytes: &[u8]) { - match str::from_utf8(cmd_bytes) { - Ok(cmd_str) => { - let trimmed = cmd_str.trim(); - if trimmed.is_empty() { - return; - } - - match execute_command(trimmed) { - Ok(_) => { - // Command executed successfully - } - Err(ParseError::UnknownCommand(cmd)) => { - println!("Error: Unknown command '{}'", cmd); - println!("Type 'help' to see available commands"); - } - Err(ParseError::UnknownOption(opt)) => { - println!("Error: Unknown option '{}'", opt); - } - Err(ParseError::MissingValue(opt)) => { - println!("Error: Option '{}' is missing a value", opt); - } - Err(ParseError::MissingRequiredOption(opt)) => { - println!("Error: Missing required option '{}'", opt); - } - Err(ParseError::NoHandler(cmd)) => { - println!("Error: Command '{}' has no handler function", cmd); - } - } - } - Err(_) => { - println!("Error: Input contains invalid UTF-8 characters"); - } - } -} - -// Built-in command handler -pub fn handle_builtin_commands(input: &str) -> bool { - match input.trim() { - "help" => { - show_available_commands(); - true - } - "exit" | "quit" => { - println!("Goodbye!"); - std::process::exit(0); - } - "clear" => { - print!("\x1b[2J\x1b[H"); // ANSI clear screen sequence - std::io::stdout().flush().unwrap(); - true - } - _ if input.starts_with("help ") => { - let cmd_parts: Vec = input[5..] - .split_whitespace() - .map(|s| s.to_string()) - .collect(); - if let Err(e) = show_help(&cmd_parts) { - println!("Error: {:?}", e); - } - true - } - _ => false, - } -} - -pub fn show_available_commands() { - println!("ArceOS Shell - Available Commands:"); - println!(); - - // Display all top-level commands - for (name, node) in COMMAND_TREE.iter() { - println!(" {:<15} {}", name, node.description); - - // Display subcommands - if !node.subcommands.is_empty() { - for (sub_name, sub_node) in &node.subcommands { - println!(" {:<13} {}", sub_name, sub_node.description); - } - } - } - - println!(); - println!("Built-in Commands:"); - println!(" help Show help information"); - println!(" help Show help for a specific command"); - println!(" clear Clear the screen"); - println!(" exit/quit Exit the shell"); - println!(); - println!("Tip: Use 'help ' to see detailed usage of a command"); -} diff --git a/kernel/src/shell/command/vm.rs b/kernel/src/shell/command/vm.rs deleted file mode 100644 index c54fe2e6..00000000 --- a/kernel/src/shell/command/vm.rs +++ /dev/null @@ -1,1399 +0,0 @@ -use std::{ - collections::btree_map::BTreeMap, - println, - string::{String, ToString}, - vec::Vec, -}; - -use axvm::VMStatus; -#[cfg(feature = "fs")] -use std::fs::read_to_string; - -use crate::{ - shell::command::{CommandNode, FlagDef, OptionDef, ParsedCommand}, - vmm::{add_running_vm_count, vcpus, vm_list, with_vm}, -}; - -/// Check if a VM can transition to Running state. -/// Returns Ok(()) if the transition is valid, Err with a message otherwise. -fn can_start_vm(status: VMStatus) -> Result<(), &'static str> { - match status { - VMStatus::Loaded | VMStatus::Stopped => Ok(()), - VMStatus::Running => Err("VM is already running"), - VMStatus::Suspended => Err("VM is suspended, use 'vm resume' instead"), - VMStatus::Stopping => Err("VM is stopping, wait for it to fully stop"), - VMStatus::Loading => Err("VM is still loading"), - } -} - -/// Check if a VM can transition to Stopping state. -/// Returns Ok(()) if the transition is valid, Err with a message otherwise. -fn can_stop_vm(status: VMStatus, force: bool) -> Result<(), &'static str> { - match status { - VMStatus::Running | VMStatus::Suspended => Ok(()), - VMStatus::Stopping => { - if force { - Ok(()) - } else { - Err("VM is already stopping") - } - } - VMStatus::Stopped => Err("VM is already stopped"), - VMStatus::Loading | VMStatus::Loaded => Ok(()), // Allow stopping VMs in these states - } -} - -/// Check if a VM can be suspended. -fn can_suspend_vm(status: VMStatus) -> Result<(), &'static str> { - match status { - VMStatus::Running => Ok(()), - VMStatus::Suspended => Err("VM is already suspended"), - VMStatus::Stopped => Err("VM is stopped, cannot suspend"), - VMStatus::Stopping => Err("VM is stopping, cannot suspend"), - VMStatus::Loading => Err("VM is loading, cannot suspend"), - VMStatus::Loaded => Err("VM is not running, cannot suspend"), - } -} - -/// Check if a VM can be resumed. -fn can_resume_vm(status: VMStatus) -> Result<(), &'static str> { - match status { - VMStatus::Suspended => Ok(()), - VMStatus::Running => Err("VM is already running"), - VMStatus::Stopped => Err("VM is stopped, use 'vm start' instead"), - VMStatus::Stopping => Err("VM is stopping, cannot resume"), - VMStatus::Loading => Err("VM is loading, cannot resume"), - VMStatus::Loaded => Err("VM is not started yet, use 'vm start' instead"), - } -} - -/// Format memory size in a human-readable way. -fn format_memory_size(bytes: usize) -> String { - if bytes < 1024 { - format!("{}B", bytes) - } else if bytes < 1024 * 1024 { - format!("{}KB", bytes / 1024) - } else if bytes < 1024 * 1024 * 1024 { - format!("{}MB", bytes / (1024 * 1024)) - } else { - format!("{}GB", bytes / (1024 * 1024 * 1024)) - } -} - -// ============================================================================ -// Command Handlers -// ============================================================================ - -fn vm_help(_cmd: &ParsedCommand) { - println!("VM - virtual machine management"); - println!(); - println!("Most commonly used vm commands:"); - println!(" create Create a new virtual machine"); - println!(" start Start a virtual machine"); - println!(" stop Stop a virtual machine"); - println!(" suspend Suspend (pause) a running virtual machine"); - println!(" resume Resume a suspended virtual machine"); - println!(" restart Restart a virtual machine"); - println!(" delete Delete a virtual machine"); - println!(); - println!("Information commands:"); - println!(" list Show table of all VMs"); - println!(" show Show VM details (requires VM_ID)"); - println!(" - Default: basic information"); - println!(" - --full: complete detailed information"); - println!(" - --config: show configuration"); - println!(" - --stats: show statistics"); - println!(); - println!("Use 'vm --help' for more information on a specific command."); -} - -#[cfg(feature = "fs")] -fn vm_create(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - println!("Positional args: {:?}", args); - - if args.is_empty() { - println!("Error: No VM configuration file specified"); - println!("Usage: vm create [CONFIG_FILE]"); - return; - } - - let initial_vm_count = vm_list::get_vm_list().len(); - - for config_path in args.iter() { - println!("Creating VM from config: {}", config_path); - - use crate::vmm::config::init_guest_vm; - match read_to_string(config_path) { - Ok(raw_cfg) => match init_guest_vm(&raw_cfg) { - Ok(vm_id) => { - println!( - "✓ Successfully created VM[{}] from config: {}", - vm_id, config_path - ); - } - Err(_) => { - println!( - "✗ Failed to create VM from {}: Configuration error or panic occurred", - config_path - ); - } - }, - Err(e) => { - println!("✗ Failed to read config file {}: {:?}", config_path, e); - } - } - } - - // Check the actual number of VMs created - let final_vm_count = vm_list::get_vm_list().len(); - let created_count = final_vm_count - initial_vm_count; - - if created_count > 0 { - println!("Successfully created {} VM(s)", created_count); - println!("Use 'vm start ' to start the created VMs."); - } else { - println!("No VMs were created."); - } -} - -#[cfg(feature = "fs")] -fn vm_start(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let detach = cmd.flags.get("detach").unwrap_or(&false); - - if args.is_empty() { - // start all VMs - info!("VMM starting, booting all VMs..."); - let mut started_count = 0; - - for vm in vm_list::get_vm_list() { - // Check current status before starting - let status: VMStatus = vm.vm_status(); - if status == VMStatus::Running { - println!("⚠ VM[{}] is already running, skipping", vm.id()); - continue; - } - - if status != VMStatus::Loaded && status != VMStatus::Stopped { - println!("⚠ VM[{}] is in {:?} state, cannot start", vm.id(), status); - continue; - } - - if let Err(e) = start_single_vm(vm.clone()) { - println!("✗ VM[{}] failed to start: {:?}", vm.id(), e); - } else { - println!("✓ VM[{}] started successfully", vm.id()); - started_count += 1; - } - } - println!("Started {} VM(s)", started_count); - } else { - // Start specified VMs - for vm_name in args { - // Try to parse as VM ID or lookup VM name - if let Ok(vm_id) = vm_name.parse::() { - start_vm_by_id(vm_id); - } else { - println!("Error: VM name lookup not implemented. Use VM ID instead."); - println!("Available VMs:"); - vm_list_simple(); - } - } - } - - if *detach { - println!("VMs started in background mode"); - } -} - -/// Start a single VM by setting up vCPUs and calling boot. -/// Returns Ok(()) if successful, Err otherwise. -fn start_single_vm(vm: crate::vmm::VMRef) -> Result<(), &'static str> { - let vm_id = vm.id(); - let status = vm.vm_status(); - - // Validate state transition using helper function - can_start_vm(status)?; - - // Set up primary virtual CPU before starting - vcpus::setup_vm_primary_vcpu(vm.clone()); - - // Boot the VM - match vm.boot() { - Ok(_) => { - // Transition to Running state and notify the primary VCpu - // Note: Since the VCpu task is created directly in the wait queue (blocked state), - // we can immediately notify it without waiting for it to be scheduled first. - vcpus::notify_primary_vcpu(vm_id); - add_running_vm_count(1); - Ok(()) - } - Err(err) => { - // Revert status on failure - error!("Failed to boot VM[{}]: {:?}", vm_id, err); - Err("Failed to boot VM") - } - } -} - -fn start_vm_by_id(vm_id: usize) { - match with_vm(vm_id, |vm| start_single_vm(vm.clone())) { - Some(Ok(_)) => { - println!("✓ VM[{}] started successfully", vm_id); - } - Some(Err(err)) => { - println!("✗ VM[{}] failed to start: {}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -fn vm_stop(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm stop [OPTIONS] "); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - stop_vm_by_id(vm_id, *force); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn stop_vm_by_id(vm_id: usize, force: bool) { - match with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // Validate state transition using helper function - if let Err(err) = can_stop_vm(status, force) { - println!("⚠ VM[{}] {}", vm_id, err); - return Err(err); - } - - // Print appropriate message based on status - match status { - VMStatus::Stopping if force => { - println!("Force stopping VM[{}]...", vm_id); - } - VMStatus::Running => { - if force { - println!("Force stopping VM[{}]...", vm_id); - } else { - println!("Gracefully stopping VM[{}]...", vm_id); - } - } - VMStatus::Loading | VMStatus::Loaded => { - println!( - "⚠ VM[{}] is in {:?} state, stopping anyway...", - vm_id, status - ); - } - _ => {} - } - - // Call shutdown - match vm.shutdown() { - Ok(_) => Ok(()), - Err(_err) => { - // Revert status on failure - Err("Failed to shutdown VM") - } - } - }) { - Some(Ok(_)) => { - println!("✓ VM[{}] stop signal sent successfully", vm_id); - println!( - " Note: vCPU threads will exit gracefully, VM status will transition to Stopped" - ); - } - Some(Err(err)) => { - println!("✗ Failed to stop VM[{}]: {:?}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -/// Restart a VM by stopping it (if running) and then starting it again.(functionality incomplete) -fn vm_restart(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm restart [OPTIONS] "); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - restart_vm_by_id(vm_id, *force); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn restart_vm_by_id(vm_id: usize, force: bool) { - println!("Restarting VM[{}]...", vm_id); - - // Check current status - let current_status = with_vm(vm_id, |vm| vm.vm_status()); - if current_status.is_none() { - println!("✗ VM[{}] not found", vm_id); - return; - } - - let status = current_status.unwrap(); - match status { - VMStatus::Stopped | VMStatus::Loaded => { - // VM is already stopped, just start it - println!("VM[{}] is already stopped, starting...", vm_id); - start_vm_by_id(vm_id); - } - VMStatus::Suspended | VMStatus::Running => { - // Stop the VM (this will wake up suspended VCpus automatically) - println!("Stopping VM[{}]...", vm_id); - stop_vm_by_id(vm_id, force); - - // Wait for VM to fully stop - println!("Waiting for VM[{}] to stop completely...", vm_id); - let max_wait_iterations = 50; // 5 seconds timeout (50 * 100ms) - let mut iterations = 0; - - loop { - if let Some(vm_status) = with_vm(vm_id, |vm| vm.vm_status()) { - match vm_status { - VMStatus::Stopped => { - println!("✓ VM[{}] stopped successfully", vm_id); - break; - } - VMStatus::Stopping => { - // Still stopping, wait a bit - iterations += 1; - if iterations >= max_wait_iterations { - println!( - "⚠ VM[{}] stop timeout, it may still be shutting down", - vm_id - ); - println!(" Use 'vm status {}' to check status manually", vm_id); - return; - } - // Sleep for 100ms - std::os::arceos::modules::axhal::time::busy_wait( - core::time::Duration::from_millis(100), - ); - } - _ => { - println!("⚠ VM[{}] in unexpected state: {:?}", vm_id, vm_status); - return; - } - } - } else { - println!("✗ VM[{}] no longer exists", vm_id); - return; - } - } - - // Now restart the VM - println!("Starting VM[{}]...", vm_id); - start_vm_by_id(vm_id); - } - VMStatus::Stopping => { - if force { - println!( - "⚠ VM[{}] is currently stopping, waiting for shutdown to complete...", - vm_id - ); - // Could implement similar wait logic here if needed - } else { - println!("⚠ VM[{}] is currently stopping", vm_id); - println!( - " Wait for shutdown to complete, then use 'vm start {}'", - vm_id - ); - println!(" Or use --force to wait and then restart"); - } - } - VMStatus::Loading => { - println!("✗ VM[{}] is still loading, cannot restart", vm_id); - } - } -} - -/// Suspend a running VM (functionality incomplete) -fn vm_suspend(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm suspend ..."); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - suspend_vm_by_id(vm_id); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn suspend_vm_by_id(vm_id: usize) { - println!("Suspending VM[{}]...", vm_id); - - let result = with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // Check if VM can be suspended - if let Err(err_msg) = can_suspend_vm(status) { - return Err(err_msg); - } - - // Set VM status to Suspended - vm.set_vm_status(VMStatus::Suspended); - info!("VM[{}] status set to Suspended", vm_id); - - Ok(()) - }); - - match result { - Some(Ok(_)) => { - println!("✓ VM[{}] suspend signal sent", vm_id); - - // Get VM to check VCpu count - let vcpu_count = with_vm(vm_id, |vm| vm.vcpu_num()).unwrap_or(0); - println!( - " Note: {} VCpu task(s) will enter wait queue at next VMExit", - vcpu_count - ); - - // Wait a brief moment for VCpus to enter suspended state - println!(" Waiting for VCpus to suspend..."); - let max_wait_iterations = 10; // 1 second timeout (10 * 100ms) - let mut iterations = 0; - let mut all_suspended = false; - - while iterations < max_wait_iterations { - // Check if all VCpus are in blocked state - if let Some(vm) = crate::vmm::vm_list::get_vm_by_id(vm_id) { - let vcpu_states: Vec<_> = - vm.vcpu_list().iter().map(|vcpu| vcpu.state()).collect(); - - let blocked_count = vcpu_states - .iter() - .filter(|s| matches!(s, axvcpu::VCpuState::Blocked)) - .count(); - - if blocked_count == vcpu_states.len() { - all_suspended = true; - break; - } - - // Show progress for the first few iterations - if iterations < 3 { - debug!(" VCpus blocked: {}/{}", blocked_count, vcpu_states.len()); - } - } - - iterations += 1; - std::os::arceos::modules::axhal::time::busy_wait( - core::time::Duration::from_millis(100), - ); - } - - if all_suspended { - println!("✓ All VCpu tasks are now suspended"); - } else { - println!("⚠ Some VCpu tasks may still be transitioning to suspended state"); - println!(" VCpus will suspend at next VMExit (timer interrupt, I/O, etc.)"); - println!(" This is normal for VMs with low interrupt rates"); - } - - println!(" Use 'vm resume {}' to resume the VM", vm_id); - } - Some(Err(err)) => { - println!("✗ Failed to suspend VM[{}]: {}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -// Resume a suspended VM (functionality incomplete) -fn vm_resume(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm resume ..."); - return; - } - - for vm_name in args { - if let Ok(vm_id) = vm_name.parse::() { - resume_vm_by_id(vm_id); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } - } -} - -fn resume_vm_by_id(vm_id: usize) { - println!("Resuming VM[{}]...", vm_id); - - let result = with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // Check if VM can be resumed - if let Err(err_msg) = can_resume_vm(status) { - return Err(err_msg); - } - - // Set VM status back to Running - vm.set_vm_status(VMStatus::Running); - - // Notify all VCpus to wake up - vcpus::notify_all_vcpus(vm_id); - - info!("VM[{}] resumed", vm_id); - Ok(()) - }); - - match result { - Some(Ok(_)) => { - println!("✓ VM[{}] resumed successfully", vm_id); - } - Some(Err(err)) => { - println!("✗ Failed to resume VM[{}]: {}", vm_id, err); - } - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -fn vm_delete(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let force = cmd.flags.get("force").unwrap_or(&false); - let keep_data = cmd.flags.get("keep-data").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm delete [OPTIONS] "); - return; - } - - let vm_name = &args[0]; - - if let Ok(vm_id) = vm_name.parse::() { - // Check if VM exists and get its status first - let vm_status = with_vm(vm_id, |vm| vm.vm_status()); - - if vm_status.is_none() { - println!("✗ VM[{}] not found", vm_id); - return; - } - - let status = vm_status.unwrap(); - - // Check if VM is running - match status { - VMStatus::Running => { - if !force { - println!("✗ VM[{}] is currently running", vm_id); - println!( - " Use 'vm stop {}' first, or use '--force' to force delete", - vm_id - ); - return; - } - println!("⚠ Force deleting running VM[{}]...", vm_id); - } - VMStatus::Stopping => { - if !force { - println!("⚠ VM[{}] is currently stopping", vm_id); - println!(" Wait for it to stop completely, or use '--force' to force delete"); - return; - } - println!("⚠ Force deleting stopping VM[{}]...", vm_id); - } - VMStatus::Stopped => { - println!("Deleting stopped VM[{}]...", vm_id); - } - _ => { - println!("⚠ VM[{}] is in {:?} state", vm_id, status); - if !force { - println!("Use --force to force delete"); - return; - } - } - } - - delete_vm_by_id(vm_id, *keep_data); - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } -} - -fn delete_vm_by_id(vm_id: usize, keep_data: bool) { - // First check VM status and try to stop it if running - let vm_status = with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - // If VM is running, suspended, or stopping, send shutdown signal - match status { - VMStatus::Running | VMStatus::Suspended | VMStatus::Stopping => { - println!( - " VM[{}] is {:?}, sending shutdown signal...", - vm_id, status - ); - vm.set_vm_status(VMStatus::Stopping); - let _ = vm.shutdown(); - } - VMStatus::Loaded => { - // Transition from Loaded to Stopped - vm.set_vm_status(VMStatus::Stopped); - } - _ => {} - } - - use alloc::sync::Arc; - let count = Arc::strong_count(&vm); - println!(" [Debug] VM Arc strong_count: {}", count); - - status - }); - - if vm_status.is_none() { - println!("✗ VM[{}] not found or already removed", vm_id); - return; - } - - let status = vm_status.unwrap(); - - // Remove VM from global list - // Note: This drops the reference from the global list, but the VM object - // will only be fully destroyed when all vCPU threads exit and drop their references - match crate::vmm::vm_list::remove_vm(vm_id) { - Some(vm) => { - println!("✓ VM[{}] removed from VM list", vm_id); - - // Wait for vCPU threads to exit if VM has VCpu tasks - match status { - VMStatus::Running - | VMStatus::Suspended - | VMStatus::Stopping - | VMStatus::Stopped => { - println!(" Waiting for vCPU threads to exit..."); - - // Debug: Check Arc count before cleanup - use alloc::sync::Arc; - println!( - " [Debug] VM Arc count before cleanup: {}", - Arc::strong_count(&vm) - ); - - // Clean up VCpu resources after threads have exited - println!(" Cleaning up VCpu resources..."); - vcpus::cleanup_vm_vcpus(vm_id); - - // Debug: Check Arc count after final wait - println!( - " [Debug] VM Arc count after final wait: {}", - Arc::strong_count(&vm) - ); - } - _ => { - // VM not running, no vCPU threads to wait for - // But still need to clean up VCpu queue entry if it exists - vcpus::cleanup_vm_vcpus(vm_id); - } - } - - if keep_data { - println!("✓ VM[{}] deleted (configuration and data preserved)", vm_id); - } else { - println!("✓ VM[{}] deleted completely", vm_id); - - // Debug: Check Arc count - should be 1 now (only this variable) - // TaskExt uses Weak reference, so it doesn't count - use alloc::sync::Arc; - let count = Arc::strong_count(&vm); - println!(" [Debug] VM Arc strong_count: {}", count); - - if count == 1 { - println!(" ✓ Perfect! VM will be freed immediately when function returns"); - } else { - println!( - " ⚠ Warning: Unexpected Arc count {}, possible reference leak!", - count - ); - } - - // TODO: Clean up VM-related data files - // - Remove disk images - // - Remove configuration files - // - Remove log files - } - - // When function returns, the 'vm' variable is dropped - // Since Arc count is 1, AxVM::drop() is called immediately - println!(" VM[{}] will be freed now", vm_id); - } - None => { - println!( - "✗ Failed to remove VM[{}] from list (may have been removed already)", - vm_id - ); - } - } - - // When function returns, the 'vm' Arc is dropped - // If all vCPU threads have exited (ref_count was 1), AxVM::drop() is called here - println!("✓ VM[{}] deletion completed", vm_id); -} - -#[cfg(feature = "fs")] -fn vm_list_simple() { - let vms = vm_list::get_vm_list(); - println!("ID NAME STATE VCPU MEMORY"); - println!("---- ----------- ------- ---- ------"); - for vm in vms { - let status = vm.vm_status(); - - // Calculate total memory size - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - - println!( - "{:<4} {:<11} {:<7} {:<4} {}", - vm.id(), - vm.with_config(|cfg| cfg.name()), - status.as_str(), - vm.vcpu_num(), - format_memory_size(total_memory) - ); - } -} - -fn vm_list(cmd: &ParsedCommand) { - let binding = "table".to_string(); - let format = cmd.options.get("format").unwrap_or(&binding); - - let display_vms = vm_list::get_vm_list(); - - if display_vms.is_empty() { - println!("No virtual machines found."); - return; - } - - if format == "json" { - // JSON output - println!("{{"); - println!(" \"vms\": ["); - for (i, vm) in display_vms.iter().enumerate() { - let status = vm.vm_status(); - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - - println!(" {{"); - println!(" \"id\": {},", vm.id()); - println!(" \"name\": \"{}\",", vm.with_config(|cfg| cfg.name())); - println!(" \"state\": \"{}\",", status.as_str()); - println!(" \"vcpu\": {},", vm.vcpu_num()); - println!(" \"memory\": \"{}\"", format_memory_size(total_memory)); - - if i < display_vms.len() - 1 { - println!(" }},"); - } else { - println!(" }}"); - } - } - println!(" ]"); - println!("}}"); - } else { - // Table output (default) - println!( - "{:<6} {:<15} {:<12} {:<15} {:<10} {:<20}", - "VM ID", "NAME", "STATUS", "VCPU", "MEMORY", "VCPU STATE" - ); - println!( - "{:-<6} {:-<15} {:-<12} {:-<15} {:-<10} {:-<20}", - "", "", "", "", "", "" - ); - - for vm in display_vms { - let status = vm.vm_status(); - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - - // Get VCpu ID list - let vcpu_ids: Vec = vm - .vcpu_list() - .iter() - .map(|vcpu| vcpu.id().to_string()) - .collect(); - let vcpu_id_list = vcpu_ids.join(","); - - // Get VCpu state summary - let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Run", - axvcpu::VCpuState::Blocked => "Blk", - axvcpu::VCpuState::Invalid => "Inv", - axvcpu::VCpuState::Created => "Cre", - axvcpu::VCpuState::Ready => "Rdy", - }; - *state_counts.entry(state).or_insert(0) += 1; - } - - // Format: Run:2,Blk:1 - let summary: Vec = state_counts - .iter() - .map(|(state, count)| format!("{}:{}", state, count)) - .collect(); - let vcpu_state_summary = summary.join(","); - - println!( - "{:<6} {:<15} {:<12} {:<15} {:<10} {:<20}", - vm.id(), - vm.with_config(|cfg| cfg.name()), - status.as_str(), - vcpu_id_list, - format_memory_size(total_memory), - vcpu_state_summary - ); - } - } -} - -fn vm_show(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let show_config = cmd.flags.get("config").unwrap_or(&false); - let show_stats = cmd.flags.get("stats").unwrap_or(&false); - let show_full = cmd.flags.get("full").unwrap_or(&false); - - if args.is_empty() { - println!("Error: No VM specified"); - println!("Usage: vm show [OPTIONS] "); - println!(); - println!("Options:"); - println!(" -f, --full Show full detailed information"); - println!(" -c, --config Show configuration details"); - println!(" -s, --stats Show statistics"); - println!(); - println!("Use 'vm list' to see all VMs"); - return; - } - - // Show specific VM details - let vm_name = &args[0]; - if let Ok(vm_id) = vm_name.parse::() { - if *show_full { - show_vm_full_details(vm_id); - } else { - show_vm_basic_details(vm_id, *show_config, *show_stats); - } - } else { - println!("Error: Invalid VM ID: {}", vm_name); - } -} - -/// Show basic VM information (default view) -fn show_vm_basic_details(vm_id: usize, show_config: bool, show_stats: bool) { - match with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - println!("VM Details: {}", vm_id); - println!(); - - // Basic Information - println!(" VM ID: {}", vm.id()); - println!(" Name: {}", vm.with_config(|cfg| cfg.name())); - println!(" Status: {}", status.as_str_with_icon()); - println!(" VCPUs: {}", vm.vcpu_num()); - - // Calculate total memory - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - println!(" Memory: {}", format_memory_size(total_memory)); - - // Add state-specific information - match status { - VMStatus::Suspended => { - println!(); - println!(" ℹ VM is paused. Use 'vm resume {}' to continue.", vm_id); - } - VMStatus::Stopped => { - println!(); - println!(" ℹ VM is stopped. Use 'vm delete {}' to clean up.", vm_id); - } - VMStatus::Loaded => { - println!(); - println!(" ℹ VM is ready. Use 'vm start {}' to boot.", vm_id); - } - _ => {} - } - - // VCPU Summary - println!(); - println!("VCPU Summary:"); - let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Running", - axvcpu::VCpuState::Blocked => "Blocked", - axvcpu::VCpuState::Invalid => "Invalid", - axvcpu::VCpuState::Created => "Created", - axvcpu::VCpuState::Ready => "Ready", - }; - *state_counts.entry(state).or_insert(0) += 1; - } - - for (state, count) in state_counts { - println!(" {}: {}", state, count); - } - - // Memory Summary - println!(); - println!("Memory Summary:"); - println!(" Total Regions: {}", vm.memory_regions().len()); - println!(" Total Size: {}", format_memory_size(total_memory)); - - // Configuration Summary - if show_config { - println!(); - println!("Configuration:"); - vm.with_config(|cfg| { - println!(" BSP Entry: {:#x}", cfg.bsp_entry().as_usize()); - println!(" AP Entry: {:#x}", cfg.ap_entry().as_usize()); - println!(" Interrupt Mode: {:?}", cfg.interrupt_mode()); - if let Some(dtb_addr) = cfg.image_config().dtb_load_gpa { - println!(" DTB Address: {:#x}", dtb_addr.as_usize()); - } - }); - } - - // Device Summary - if show_stats { - println!(); - println!("Device Summary:"); - println!( - " MMIO Devices: {}", - vm.get_devices().iter_mmio_dev().count() - ); - println!( - " SysReg Devices: {}", - vm.get_devices().iter_sys_reg_dev().count() - ); - } - - println!(); - println!("Use 'vm show {} --full' for detailed information", vm_id); - }) { - Some(_) => {} - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -/// Show full detailed information about a specific VM (--full flag) -fn show_vm_full_details(vm_id: usize) { - match with_vm(vm_id, |vm| { - let status = vm.vm_status(); - - println!("=== VM Details: {} ===", vm_id); - println!(); - - // Basic Information - println!("Basic Information:"); - println!(" VM ID: {}", vm.id()); - println!(" Name: {}", vm.with_config(|cfg| cfg.name())); - println!(" Status: {}", status.as_str_with_icon()); - println!(" VCPUs: {}", vm.vcpu_num()); - - // Calculate total memory - let total_memory: usize = vm.memory_regions().iter().map(|region| region.size()).sum(); - println!(" Memory: {}", format_memory_size(total_memory)); - println!(" EPT Root: {:#x}", vm.ept_root().as_usize()); - - // Add state-specific information - match status { - VMStatus::Suspended => { - println!( - " ℹ VM is paused, VCpu tasks are waiting. Use 'vm resume {}' to continue.", - vm_id - ); - } - VMStatus::Stopping => { - println!(" ℹ VM is shutting down, VCpu tasks are exiting."); - } - VMStatus::Stopped => { - println!( - " ℹ VM is stopped, all VCpu tasks have exited. Use 'vm delete {}' to clean up.", - vm_id - ); - } - VMStatus::Loaded => { - println!( - " ℹ VM is ready to start. Use 'vm start {}' to boot.", - vm_id - ); - } - _ => {} - } - - // VCPU Details - println!(); - println!("VCPU Details:"); - - // Count VCpu states for summary - let mut state_counts = std::collections::BTreeMap::new(); - for vcpu in vm.vcpu_list() { - let state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Running", - axvcpu::VCpuState::Blocked => "Blocked", - axvcpu::VCpuState::Invalid => "Invalid", - axvcpu::VCpuState::Created => "Created", - axvcpu::VCpuState::Ready => "Ready", - }; - *state_counts.entry(state).or_insert(0) += 1; - } - - // Show summary first - let summary: Vec = state_counts - .iter() - .map(|(state, count)| format!("{}: {}", state, count)) - .collect(); - println!(" Summary: {}", summary.join(", ")); - println!(); - - for vcpu in vm.vcpu_list() { - let vcpu_state = match vcpu.state() { - axvcpu::VCpuState::Free => "Free", - axvcpu::VCpuState::Running => "Running", - axvcpu::VCpuState::Blocked => "Blocked", - axvcpu::VCpuState::Invalid => "Invalid", - axvcpu::VCpuState::Created => "Created", - axvcpu::VCpuState::Ready => "Ready", - }; - - if let Some(phys_cpu_set) = vcpu.phys_cpu_set() { - println!( - " VCPU {}: {} (Affinity: {:#x})", - vcpu.id(), - vcpu_state, - phys_cpu_set - ); - } else { - println!(" VCPU {}: {} (No affinity)", vcpu.id(), vcpu_state); - } - } - - // Add note for Suspended VMs - if status == VMStatus::Suspended { - println!(); - println!( - " Note: VCpu tasks are blocked in wait queue and will resume when VM is unpaused." - ); - } - - // Memory Regions - println!(); - println!( - "Memory Regions: ({} region(s), {} total)", - vm.memory_regions().len(), - format_memory_size(total_memory) - ); - for (i, region) in vm.memory_regions().iter().enumerate() { - let region_type = if region.needs_dealloc { - "Allocated" - } else { - "Reserved" - }; - let identical = if region.is_identical() { - " [identical]" - } else { - "" - }; - println!( - " Region {}: GPA={:#x} HVA={:#x} Size={} Type={}{}", - i, - region.gpa, - region.hva, - format_memory_size(region.size()), - region_type, - identical - ); - } - - // Configuration - println!(); - println!("Configuration:"); - vm.with_config(|cfg| { - println!(" BSP Entry: {:#x}", cfg.bsp_entry().as_usize()); - println!(" AP Entry: {:#x}", cfg.ap_entry().as_usize()); - println!(" Interrupt Mode: {:?}", cfg.interrupt_mode()); - - if let Some(dtb_addr) = cfg.image_config().dtb_load_gpa { - println!(" DTB Address: {:#x}", dtb_addr.as_usize()); - } - - // Show kernel info - println!( - " Kernel GPA: {:#x}", - cfg.image_config().kernel_load_gpa.as_usize() - ); - - // Show passthrough devices - if !cfg.pass_through_devices().is_empty() { - println!(); - println!( - " Passthrough Devices: ({} device(s))", - cfg.pass_through_devices().len() - ); - for device in cfg.pass_through_devices() { - println!( - " - {}: GPA[{:#x}~{:#x}] -> HPA[{:#x}~{:#x}] ({})", - device.name, - device.base_gpa, - device.base_gpa + device.length, - device.base_hpa, - device.base_hpa + device.length, - format_memory_size(device.length) - ); - } - } - - // Show passthrough addresses - if !cfg.pass_through_addresses().is_empty() { - println!(); - println!( - " Passthrough Memory Regions: ({} region(s))", - cfg.pass_through_addresses().len() - ); - for pt_addr in cfg.pass_through_addresses() { - println!( - " - GPA[{:#x}~{:#x}] ({})", - pt_addr.base_gpa, - pt_addr.base_gpa + pt_addr.length, - format_memory_size(pt_addr.length) - ); - } - } - - // Show passthrough SPIs (ARM specific) - #[cfg(target_arch = "aarch64")] - { - let spis = cfg.pass_through_spis(); - if !spis.is_empty() { - println!(); - println!(" Passthrough SPIs: {:?}", spis); - } - } - - // Show emulated devices - if !cfg.emu_devices().is_empty() { - println!(); - println!( - " Emulated Devices: ({} device(s))", - cfg.emu_devices().len() - ); - for (idx, device) in cfg.emu_devices().iter().enumerate() { - println!(" {}. {:?}", idx + 1, device); - } - } - }); - - // Devices - println!(); - let mmio_dev_count = vm.get_devices().iter_mmio_dev().count(); - let sysreg_dev_count = vm.get_devices().iter_sys_reg_dev().count(); - println!("Devices:"); - println!(" MMIO Devices: {}", mmio_dev_count); - println!(" SysReg Devices: {}", sysreg_dev_count); - - // Additional Statistics - println!(); - println!("Additional Statistics:"); - println!(" Total Memory Regions: {}", vm.memory_regions().len()); - - // Show VCpu affinity details - println!(); - println!(" VCpu Affinity Details:"); - for (vcpu_id, affinity, pcpu_id) in vm.get_vcpu_affinities_pcpu_ids() { - if let Some(aff) = affinity { - println!( - " VCpu {}: Physical CPU mask {:#x}, PCpu ID {}", - vcpu_id, aff, pcpu_id - ); - } else { - println!( - " VCpu {}: No specific affinity, PCpu ID {}", - vcpu_id, pcpu_id - ); - } - } - }) { - Some(_) => {} - None => { - println!("✗ VM[{}] not found", vm_id); - } - } -} - -/// Build the VM command tree and register it. -pub fn build_vm_cmd(tree: &mut BTreeMap) { - #[cfg(feature = "fs")] - let create_cmd = CommandNode::new("Create a new virtual machine") - .with_handler(vm_create) - .with_usage("vm create [OPTIONS] ...") - .with_option( - OptionDef::new("name", "Virtual machine name") - .with_short('n') - .with_long("name"), - ) - .with_option( - OptionDef::new("cpu", "Number of CPU cores") - .with_short('c') - .with_long("cpu"), - ) - .with_option( - OptionDef::new("memory", "Amount of memory") - .with_short('m') - .with_long("memory"), - ) - .with_flag( - FlagDef::new("force", "Force creation without confirmation") - .with_short('f') - .with_long("force"), - ); - - #[cfg(feature = "fs")] - let start_cmd = CommandNode::new("Start a virtual machine") - .with_handler(vm_start) - .with_usage("vm start [OPTIONS] [VM_ID...]") - .with_flag( - FlagDef::new("detach", "Start in background") - .with_short('d') - .with_long("detach"), - ) - .with_flag( - FlagDef::new("console", "Attach to console") - .with_short('c') - .with_long("console"), - ); - - let stop_cmd = CommandNode::new("Stop a virtual machine") - .with_handler(vm_stop) - .with_usage("vm stop [OPTIONS] ...") - .with_flag( - FlagDef::new("force", "Force stop") - .with_short('f') - .with_long("force"), - ) - .with_flag( - FlagDef::new("graceful", "Graceful shutdown") - .with_short('g') - .with_long("graceful"), - ); - - let restart_cmd = CommandNode::new("Restart a virtual machine") - .with_handler(vm_restart) - .with_usage("vm restart [OPTIONS] ...") - .with_flag( - FlagDef::new("force", "Force restart") - .with_short('f') - .with_long("force"), - ); - - let suspend_cmd = CommandNode::new("Suspend (pause) a running virtual machine") - .with_handler(vm_suspend) - .with_usage("vm suspend ..."); - - let resume_cmd = CommandNode::new("Resume a suspended virtual machine") - .with_handler(vm_resume) - .with_usage("vm resume ..."); - - let delete_cmd = CommandNode::new("Delete a virtual machine") - .with_handler(vm_delete) - .with_usage("vm delete [OPTIONS] ") - .with_flag( - FlagDef::new("force", "Skip confirmation") - .with_short('f') - .with_long("force"), - ) - .with_flag(FlagDef::new("keep-data", "Keep VM data").with_long("keep-data")); - - let list_cmd = CommandNode::new("Show virtual machine lists") - .with_handler(vm_list) - .with_usage("vm list [OPTIONS]") - .with_flag( - FlagDef::new("all", "Show all VMs including stopped ones") - .with_short('a') - .with_long("all"), - ) - .with_option(OptionDef::new("format", "Output format (table, json)").with_long("format")); - - let show_cmd = CommandNode::new("Show detailed VM information") - .with_handler(vm_show) - .with_usage("vm show [OPTIONS] ") - .with_flag( - FlagDef::new("full", "Show full detailed information") - .with_short('f') - .with_long("full"), - ) - .with_flag( - FlagDef::new("config", "Show configuration details") - .with_short('c') - .with_long("config"), - ) - .with_flag( - FlagDef::new("stats", "Show device statistics") - .with_short('s') - .with_long("stats"), - ); - - // main VM command - let mut vm_node = CommandNode::new("Virtual machine management") - .with_handler(vm_help) - .with_usage("vm [options] [args...]") - .add_subcommand( - "help", - CommandNode::new("Show VM help").with_handler(vm_help), - ); - - #[cfg(feature = "fs")] - { - vm_node = vm_node - .add_subcommand("create", create_cmd) - .add_subcommand("start", start_cmd); - } - - vm_node = vm_node - .add_subcommand("stop", stop_cmd) - .add_subcommand("suspend", suspend_cmd) - .add_subcommand("resume", resume_cmd) - .add_subcommand("restart", restart_cmd) - .add_subcommand("delete", delete_cmd) - .add_subcommand("list", list_cmd) - .add_subcommand("show", show_cmd); - - tree.insert("vm".to_string(), vm_node); -} diff --git a/kernel/src/shell/commands/builtin.rs b/kernel/src/shell/commands/builtin.rs new file mode 100644 index 00000000..c71f27ed --- /dev/null +++ b/kernel/src/shell/commands/builtin.rs @@ -0,0 +1,129 @@ +//! Built-in commands +//! +//! Commands that are part of the shell itself (help, exit, clear, log, uname). + +use std::println; + +use super::super::parser::ParsedCommand; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; + +/// Handle the `uname` command - display system information +pub fn do_uname(cmd: &ParsedCommand) { + let show_all = cmd.flags.get("all").unwrap_or(&false); + let show_kernel = cmd.flags.get("kernel-name").unwrap_or(&false); + let show_arch = cmd.flags.get("machine").unwrap_or(&false); + + let arch = option_env!("AX_ARCH").unwrap_or(""); + let platform = option_env!("AX_PLATFORM").unwrap_or(""); + let smp = match option_env!("AX_SMP") { + None | Some("1") => "", + _ => " SMP", + }; + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.1.0"); + + if *show_all { + println!( + "ArceOS {ver}{smp} {arch} {plat}", + ver = version, + smp = smp, + arch = arch, + plat = platform, + ); + } else if *show_kernel { + println!("ArceOS"); + } else if *show_arch { + println!("{}", arch); + } else { + println!( + "ArceOS {ver}{smp} {arch} {plat}", + ver = version, + smp = smp, + arch = arch, + plat = platform, + ); + } +} + +/// Handle the `exit` command - exit the shell +pub fn do_exit(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + let exit_code = if args.is_empty() { + 0 + } else { + args[0].parse::().unwrap_or(0) + }; + + println!("Bye~"); + std::process::exit(exit_code); +} + +/// Handle the `log` command - change log level +pub fn do_log(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + println!("Current log level: {:?}", log::max_level()); + return; + } + + match args[0].as_str() { + "on" | "enable" => log::set_max_level(log::LevelFilter::Info), + "off" | "disable" => log::set_max_level(log::LevelFilter::Off), + "error" => log::set_max_level(log::LevelFilter::Error), + "warn" => log::set_max_level(log::LevelFilter::Warn), + "info" => log::set_max_level(log::LevelFilter::Info), + "debug" => log::set_max_level(log::LevelFilter::Debug), + "trace" => log::set_max_level(log::LevelFilter::Trace), + level => { + println!("Unknown log level: {}", level); + println!("Available levels: off, error, warn, info, debug, trace"); + return; + } + } + println!("Log level set to: {:?}", log::max_level()); +} + +/// Register built-in commands to the command tree +pub fn register_builtin_commands(tree: &mut BTreeMap) { + use super::super::parser::{CommandNode, FlagDef}; + + // uname Command + tree.insert( + "uname".to_string(), + CommandNode::new("System information") + .with_handler(do_uname) + .with_usage("uname [OPTIONS]") + .with_flag( + FlagDef::new("all", "Show all information") + .with_short('a') + .with_long("all"), + ) + .with_flag( + FlagDef::new("kernel-name", "Show kernel name") + .with_short('s') + .with_long("kernel-name"), + ) + .with_flag( + FlagDef::new("machine", "Show machine architecture") + .with_short('m') + .with_long("machine"), + ), + ); + + // exit Command + tree.insert( + "exit".to_string(), + CommandNode::new("Exit the shell") + .with_handler(do_exit) + .with_usage("exit [EXIT_CODE]"), + ); + + // log Command + tree.insert( + "log".to_string(), + CommandNode::new("Change log level") + .with_handler(do_log) + .with_usage("log [LEVEL]"), + ); +} diff --git a/kernel/src/shell/command/base.rs b/kernel/src/shell/commands/fs.rs similarity index 84% rename from kernel/src/shell/command/base.rs rename to kernel/src/shell/commands/fs.rs index d24b945d..fe2c03df 100644 --- a/kernel/src/shell/command/base.rs +++ b/kernel/src/shell/commands/fs.rs @@ -1,3 +1,7 @@ +//! File system commands +//! +//! Commands for file and directory operations (ls, cat, cd, mkdir, etc.). + use std::collections::BTreeMap; #[cfg(feature = "fs")] use std::fs::{self, File, FileType}; @@ -6,7 +10,7 @@ use std::io::{self, Read, Write}; use std::println; use std::string::{String, ToString}; -use crate::shell::command::{CommandNode, FlagDef, ParsedCommand}; +use super::super::parser::{CommandNode, FlagDef, ParsedCommand}; #[cfg(feature = "fs")] macro_rules! print_err { @@ -18,17 +22,9 @@ macro_rules! print_err { }; } -// Helper function: split whitespace -#[cfg(feature = "fs")] -fn split_whitespace(s: &str) -> (&str, &str) { - let s = s.trim(); - if let Some(pos) = s.find(char::is_whitespace) { - let (first, rest) = s.split_at(pos); - (first, rest.trim()) - } else { - (s, "") - } -} +// ============================================================================ +// Command Handlers +// ============================================================================ #[cfg(feature = "fs")] fn do_ls(cmd: &ParsedCommand) { @@ -286,79 +282,6 @@ fn do_pwd(cmd: &ParsedCommand) { println!("{}", pwd); } -fn do_uname(cmd: &ParsedCommand) { - let show_all = cmd.flags.get("all").unwrap_or(&false); - let show_kernel = cmd.flags.get("kernel-name").unwrap_or(&false); - let show_arch = cmd.flags.get("machine").unwrap_or(&false); - - let arch = option_env!("AX_ARCH").unwrap_or(""); - let platform = option_env!("AX_PLATFORM").unwrap_or(""); - let smp = match option_env!("AX_SMP") { - None | Some("1") => "", - _ => " SMP", - }; - let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.1.0"); - - if *show_all { - println!( - "ArceOS {ver}{smp} {arch} {plat}", - ver = version, - smp = smp, - arch = arch, - plat = platform, - ); - } else if *show_kernel { - println!("ArceOS"); - } else if *show_arch { - println!("{}", arch); - } else { - println!( - "ArceOS {ver}{smp} {arch} {plat}", - ver = version, - smp = smp, - arch = arch, - plat = platform, - ); - } -} - -fn do_exit(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - let exit_code = if args.is_empty() { - 0 - } else { - args[0].parse::().unwrap_or(0) - }; - - println!("Bye~"); - std::process::exit(exit_code); -} - -fn do_log(cmd: &ParsedCommand) { - let args = &cmd.positional_args; - - if args.is_empty() { - println!("Current log level: {:?}", log::max_level()); - return; - } - - match args[0].as_str() { - "on" | "enable" => log::set_max_level(log::LevelFilter::Info), - "off" | "disable" => log::set_max_level(log::LevelFilter::Off), - "error" => log::set_max_level(log::LevelFilter::Error), - "warn" => log::set_max_level(log::LevelFilter::Warn), - "info" => log::set_max_level(log::LevelFilter::Info), - "debug" => log::set_max_level(log::LevelFilter::Debug), - "trace" => log::set_max_level(log::LevelFilter::Trace), - level => { - println!("Unknown log level: {}", level); - println!("Available levels: off, error, warn, info, debug, trace"); - return; - } - } - println!("Log level set to: {:?}", log::max_level()); -} - #[cfg(feature = "fs")] fn do_mv(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -605,7 +528,23 @@ const fn file_perm_to_rwx(mode: u32) -> [u8; 9] { perm } -pub fn build_base_cmd(tree: &mut BTreeMap) { +// Helper function: split whitespace +#[cfg(feature = "fs")] +fn split_whitespace(s: &str) -> (&str, &str) { + let s = s.trim(); + if let Some(pos) = s.find(char::is_whitespace) { + let (first, rest) = s.split_at(pos); + (first, rest.trim()) + } else { + (s, "") + } +} + +// ============================================================================ +// Command Registration +// ============================================================================ + +pub fn register_fs_commands(tree: &mut BTreeMap) { // ls Command #[cfg(feature = "fs")] tree.insert( @@ -709,45 +648,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ), ); - // uname Command - tree.insert( - "uname".to_string(), - CommandNode::new("System information") - .with_handler(do_uname) - .with_usage("uname [OPTIONS]") - .with_flag( - FlagDef::new("all", "Show all information") - .with_short('a') - .with_long("all"), - ) - .with_flag( - FlagDef::new("kernel-name", "Show kernel name") - .with_short('s') - .with_long("kernel-name"), - ) - .with_flag( - FlagDef::new("machine", "Show machine architecture") - .with_short('m') - .with_long("machine"), - ), - ); - - // exit Command - tree.insert( - "exit".to_string(), - CommandNode::new("Exit the shell") - .with_handler(do_exit) - .with_usage("exit [EXIT_CODE]"), - ); - - // log Command - tree.insert( - "log".to_string(), - CommandNode::new("Change log level") - .with_handler(do_log) - .with_usage("log [LEVEL]"), - ); - // touch Command #[cfg(feature = "fs")] tree.insert( diff --git a/kernel/src/shell/commands/mod.rs b/kernel/src/shell/commands/mod.rs new file mode 100644 index 00000000..e93c0bec --- /dev/null +++ b/kernel/src/shell/commands/mod.rs @@ -0,0 +1,239 @@ +//! Command handlers module +//! +//! Provides command implementations for different categories. + +mod builtin; +mod fs; +mod vm; + +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +use core::str; + +use crate::std::io::Write; + +use axstd::print; +use axstd::println; + +use super::parser::{CommandNode, CommandParser, ParseError}; + +pub use builtin::register_builtin_commands; +pub use fs::register_fs_commands; +pub use vm::register_vm_commands; + +lazy_static::lazy_static! { + /// Global command tree containing all registered commands + pub static ref COMMAND_TREE: BTreeMap = build_command_tree(); +} + +/// Build the complete command tree by registering all command categories +fn build_command_tree() -> BTreeMap { + let mut tree = BTreeMap::new(); + + register_builtin_commands(&mut tree); + register_fs_commands(&mut tree); + register_vm_commands(&mut tree); + + tree +} + +/// Execute a parsed command +pub fn execute_command(input: &str) -> Result<(), ParseError> { + let parsed = CommandParser::parse(input, &*COMMAND_TREE)?; + + // Find the corresponding command node + let mut current_node = (*COMMAND_TREE).get(&parsed.command_path[0]).ok_or_else(|| { + ParseError::UnknownCommand(parsed.command_path[0].clone()) + })?; + for cmd in &parsed.command_path[1..] { + current_node = current_node.subcommands.get(cmd).ok_or_else(|| { + ParseError::UnknownCommand(cmd.clone()) + })?; + } + + // Execute the command + if let Some(handler) = current_node.handler() { + handler(&parsed); + Ok(()) + } else { + Err(ParseError::NoHandler(parsed.command_path.join(" "))) + } +} + +/// Display help for a specific command +pub fn show_help(command_path: &[String]) -> Result<(), ParseError> { + let mut current_node = (*COMMAND_TREE) + .get(&command_path[0]) + .ok_or_else(|| ParseError::UnknownCommand(command_path[0].clone()))?; + + for cmd in &command_path[1..] { + current_node = current_node + .subcommands + .get(cmd) + .ok_or_else(|| ParseError::UnknownCommand(cmd.clone()))?; + } + + println!("Command: {}", command_path.join(" ")); + println!("Description: {}", current_node.description); + + if let Some(usage) = current_node.usage { + println!("Usage: {}", usage); + } + + if !current_node.options.is_empty() { + println!("\nOptions:"); + for option in ¤t_node.options { + let mut opt_str = String::new(); + if let Some(short) = option.short { + opt_str.push_str(&format!("-{short}")); + } + if let Some(long) = option.long { + if !opt_str.is_empty() { + opt_str.push_str(", "); + } + opt_str.push_str(&format!("--{long}")); + } + if opt_str.is_empty() { + opt_str = option.name.to_string(); + } + + let required_str = if option.required { " (required)" } else { "" }; + println!(" {:<20} {}{}", opt_str, option.description, required_str); + } + } + + if !current_node.flags.is_empty() { + println!("\nFlags:"); + for flag in ¤t_node.flags { + let mut flag_str = String::new(); + if let Some(short) = flag.short { + flag_str.push_str(&format!("-{short}")); + } + if let Some(long) = flag.long { + if !flag_str.is_empty() { + flag_str.push_str(", "); + } + flag_str.push_str(&format!("--{long}")); + } + if flag_str.is_empty() { + flag_str = flag.name.to_string(); + } + + println!(" {:<20} {}", flag_str, flag.description); + } + } + + if !current_node.subcommands.is_empty() { + println!("\nSubcommands:"); + for (name, node) in ¤t_node.subcommands { + println!(" {:<20} {}", name, node.description); + } + } + + Ok(()) +} + +/// Show all available commands +pub fn show_available_commands() { + println!("ArceOS Shell - Available Commands:"); + println!(); + + // Display all top-level commands + for (name, node) in (*COMMAND_TREE).iter() { + println!(" {:<15} {}", name, node.description); + + // Display subcommands + if !node.subcommands.is_empty() { + for (sub_name, sub_node) in &node.subcommands { + println!(" {:<13} {}", sub_name, sub_node.description); + } + } + } + + println!(); + println!("Built-in Commands:"); + println!(" help Show help information"); + println!(" help Show help for a specific command"); + println!(" clear Clear the screen"); + println!(" exit/quit Exit the shell"); + println!(); + println!("Tip: Use 'help ' to see detailed usage of a command"); +} + +/// Handle built-in shell commands (help, exit, clear) +pub fn handle_builtin_commands(input: &str) -> bool { + match input.trim() { + "help" => { + show_available_commands(); + true + } + "exit" | "quit" => { + println!("Goodbye!"); + axstd::process::exit(0); + } + "clear" => { + print!("\x1b[2J\x1b[H"); // ANSI clear screen sequence + axstd::io::stdout().flush().unwrap(); + true + } + _ if input.starts_with("help ") => { + let cmd_parts: Vec = input[5..] + .split_whitespace() + .map(|s| s.to_string()) + .collect(); + if let Err(e) = show_help(&cmd_parts) { + println!("Error: {:?}", e); + } + true + } + _ => false, + } +} + +/// Print the shell prompt +pub fn print_prompt() { + #[cfg(feature = "fs")] + print!("axvisor:{}$ ", axstd::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + print!("axvisor:$ "); + axstd::io::stdout().flush().unwrap(); +} + +/// Execute a command from byte input +pub fn run_cmd_bytes(cmd_bytes: &[u8]) { + match str::from_utf8(cmd_bytes) { + Ok(cmd_str) => { + let trimmed = cmd_str.trim(); + if trimmed.is_empty() { + return; + } + + match execute_command(trimmed) { + Ok(_) => { + // Command executed successfully + } + Err(ParseError::UnknownCommand(cmd)) => { + println!("Error: Unknown command '{}'", cmd); + println!("Type 'help' to see available commands"); + } + Err(ParseError::UnknownOption(opt)) => { + println!("Error: Unknown option '{}'", opt); + } + Err(ParseError::MissingValue(opt)) => { + println!("Error: Option '{}' is missing a value", opt); + } + Err(ParseError::MissingRequiredOption(opt)) => { + println!("Error: Missing required option '{}'", opt); + } + Err(ParseError::NoHandler(cmd)) => { + println!("Error: Command '{}' has no handler function", cmd); + } + } + } + Err(_) => { + println!("Error: Input contains invalid UTF-8 characters"); + } + } +} diff --git a/kernel/src/shell/commands/vm.rs b/kernel/src/shell/commands/vm.rs new file mode 100644 index 00000000..cc2d2543 --- /dev/null +++ b/kernel/src/shell/commands/vm.rs @@ -0,0 +1,680 @@ +//! Virtual machine management commands +//! +//! Commands for managing virtual machines (create, start, stop, list, etc.). + +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +#[cfg(feature = "fs")] +use axstd::fs::read_to_string; +use axstd::println; + +use crate::vmm::vm_list; +use crate::vmm::{config::build_vmconfig, start_vm}; +use axvm::config::AxVMCrateConfig; +use axvm::VMStatus; + +use super::super::parser::{CommandNode, FlagDef, OptionDef, ParsedCommand}; + +/// Format memory size in a human-readable way. +fn format_memory_size(bytes: usize) -> String { + if bytes < 1024 { + format!("{}B", bytes) + } else if bytes < 1024 * 1024 { + format!("{}KB", bytes / 1024) + } else if bytes < 1024 * 1024 * 1024 { + format!("{}MB", bytes / (1024 * 1024)) + } else { + format!("{}GB", bytes / (1024 * 1024 * 1024)) + } +} + +// ============================================================================ +// Command Handlers +// ============================================================================ + +fn vm_help(_cmd: &ParsedCommand) { + println!("VM - virtual machine management"); + println!(); + println!("Most commonly used vm commands:"); + println!(" create Create a new virtual machine"); + println!(" start Start a virtual machine"); + println!(" stop Stop a virtual machine"); + println!(" suspend Suspend (pause) a running virtual machine"); + println!(" resume Resume a suspended virtual machine"); + println!(" delete Delete a virtual machine"); + println!(); + println!("Information commands:"); + println!(" list Show table of all VMs"); + println!(" show Show VM details (requires VM_ID)"); + println!(" - Default: basic information"); + println!(" - --full: complete detailed information"); + println!(" - --config: show configuration"); + println!(" - --stats: show statistics"); + println!(); + println!("Use 'vm --help' for more information on a specific command."); +} + +#[cfg(feature = "fs")] +fn vm_create(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + println!("Positional args: {:?}", args); + + if args.is_empty() { + println!("Error: No VM configuration file specified"); + println!("Usage: vm create [CONFIG_FILE]"); + return; + } + + let initial_vm_count = vm_list::get_vm_list().len(); + + for config_path in args.iter() { + println!("Creating VM from config: {}", config_path); + + // Read file content first + let raw_cfg = match read_to_string(config_path) { + Ok(content) => content, + Err(e) => { + println!("✗ Failed to read config file {}: {:?}", config_path, e); + continue; + } + }; + + // Parse TOML from string content + let config_info: AxVMCrateConfig = match toml::from_str(&raw_cfg) { + Ok(cfg) => cfg, + Err(e) => { + println!("✗ Failed to parse TOML from {}: {:?}", config_path, e); + continue; + } + }; + + match build_vmconfig(config_info) { + Ok(vm_config) => { + match axvm::Vm::new(vm_config) { + Ok(vm) => { + let vm = vm_list::push_vm(vm); + let vm_id = vm.id(); + println!( + "✓ Successfully created VM[{}] from config: {}", + vm_id, config_path + ); + println!("{:?}", vm.status()); + } + Err(e) => { + println!( + "✗ Failed to create VM from {}: {:?}", + config_path, e + ); + } + } + } + Err(e) => { + println!("✗ Failed to build VM config from {}: {:?}", config_path, e); + } + } + } + + // Check the actual number of VMs created + let final_vm_count = vm_list::get_vm_list().len(); + let created_count = final_vm_count - initial_vm_count; + + if created_count > 0 { + println!("Successfully created {} VM(s)", created_count); + println!("Use 'vm start ' to start the created VMs."); + } else { + println!("No VMs were created."); + } +} + +#[cfg(feature = "fs")] +fn vm_start(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + // start all VMs + info!("VMM starting, booting all VMs..."); + let mut started_count = 0; + + for vm in vm_list::get_vm_list() { + let vm: vm_list::VMRef = vm; + // Check current status before starting + let status = vm.status(); + if status == VMStatus::Running { + println!("⚠ VM[{}] is already running, skipping", vm.id()); + continue; + } + + if status != VMStatus::Inited && status != VMStatus::Stopped { + println!("⚠ VM[{}] is in {:?} state, cannot start", vm.id(), status); + continue; + } + + // Use vm.id() to get usize VM ID + let vm_id = usize::from(vm.id()); + + // Try to start the VM + match vm.boot() { + Ok(_) => { + println!("✓ VM[{}] started successfully", vm_id); + started_count += 1; + } + Err(e) => { + println!("✗ VM[{}] failed to start: {:?}", vm_id, e); + } + } + } + println!("Started {} VM(s)", started_count); + } else { + // Start specified VMs + for arg in args { + // Try to parse as VM ID or lookup VM name + let arg: &String = arg; + if let Ok(vm_id) = arg.parse::() { + if !start_vm_by_id(vm_id) { + // VM not found, show available VMs + println!("Available VMs:"); + vm_list_simple(); + } + } else { + println!("Error: VM name lookup not implemented. Use VM ID instead."); + println!("Available VMs:"); + vm_list_simple(); + } + } + } +} + +fn start_vm_by_id(vm_id: usize) -> bool { + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return false; + } + }; + + // Boot the VM + match vm.boot() { + Ok(_) => { + println!("{:?}", vm.status()); + println!("✓ VM[{}] started successfully", vm_id); + true + } + Err(e) => { + println!("✗ VM[{}] failed to boot: {:?}", vm_id, e); + true + } + } +} + +fn vm_status(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + // If no arguments, show status of all VMs + if args.is_empty() { + let vm_list = vm_list::get_vm_list(); + if vm_list.is_empty() { + println!("No VMs found."); + return; + } + println!("VM Status:"); + println!("-----------"); + for vm in vm_list { + let vm: vm_list::VMRef = vm; + let vm_id = usize::from(vm.id()); + let name = vm.name(); + let status = vm.status(); + println!("VM[{}] \"{}\": {:?}", vm_id, name, status); + } + return; + } + + // Show status of specified VM(s) + for arg in args { + let arg: &String = arg; + if let Ok(vm_id) = arg.parse::() { + if let Some(vm) = vm_list::get_vm_by_id(vm_id) { + let vm: vm_list::VMRef = vm; + let name = vm.name(); + let status = vm.status(); + println!("VM[{}] \"{}\": {:?}", vm_id, name, status); + } else { + println!("✗ VM[{}] not found", vm_id); + } + } else { + println!("Error: Invalid VM ID: {}", arg); + } + } +} + +fn vm_stop(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + println!("Error: No VM specified"); + println!("Usage: vm stop "); + return; + } + + for vm_name in args { + let vm_name: &String = vm_name; + if let Ok(vm_id) = vm_name.parse::() { + stop_vm_by_id(vm_id); + } else { + println!("Error: Invalid VM ID: {}", vm_name); + } + } +} + +fn stop_vm_by_id(vm_id: usize) { + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return; + } + }; + + let status = vm.status(); + + // Check if VM can be stopped + match status { + VMStatus::Running => { + println!("Stopping VM[{}]...", vm_id); + } + VMStatus::Stopping => { + println!("⚠ VM[{}] is already stopping", vm_id); + return; + } + VMStatus::Stopped => { + println!("⚠ VM[{}] is already stopped", vm_id); + return; + } + VMStatus::Inited => { + println!("⚠ VM[{}] is not running yet", vm_id); + return; + } + _ => { + println!("⚠ VM[{}] is in {:?} state, cannot stop", vm_id, status); + return; + } + } + + // Call shutdown + match vm.shutdown() { + Ok(_) => { + println!("✓ VM[{}] stop signal sent successfully", vm_id); + println!(" Note: VM status will transition to Stopped"); + } + Err(e) => { + println!("✗ Failed to stop VM[{}]: {:?}", vm_id, e); + } + } +} + +fn vm_delete(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + let force = cmd.flags.get("force").unwrap_or(&false); + + if args.is_empty() { + println!("Error: No VM specified"); + println!("Usage: vm delete [OPTIONS] "); + return; + } + + for arg in args { + let arg: &String = arg; + if let Ok(vm_id) = arg.parse::() { + delete_vm_by_id(vm_id, *force); + } else { + println!("Error: Invalid VM ID: {}", arg); + } + } +} + +fn delete_vm_by_id(vm_id: usize, force: bool) { + // Check if VM exists and get its status + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return; + } + }; + + let status = vm.status(); + + // Check if VM is running + match status { + VMStatus::Running => { + if !force { + println!("✗ VM[{}] is currently running", vm_id); + println!(" Use 'vm stop {}' first, or use '--force' to force delete", vm_id); + return; + } + println!("⚠ Force deleting running VM[{}]...", vm_id); + } + VMStatus::Stopping => { + if !force { + println!("⚠ VM[{}] is currently stopping", vm_id); + println!(" Wait for it to stop completely, or use '--force' to force delete"); + return; + } + println!("⚠ Force deleting stopping VM[{}]...", vm_id); + } + VMStatus::Stopped | VMStatus::Inited => { + println!("Deleting VM[{}] (status: {:?})...", vm_id, status); + // Resources will be automatically released when VM is dropped + println!(" ✓ VM resources will be released on drop"); + } + _ => { + println!("⚠ VM[{}] is in {:?} state", vm_id, status); + if !force { + println!(" Use --force to force delete"); + return; + } + println!(" Force deleting..."); + } + } + + // If VM is running, try to stop it first + if matches!(status, VMStatus::Running | VMStatus::Stopping) { + println!(" Sending shutdown signal..."); + match vm.shutdown() { + Ok(_) => { + println!(" ✓ Shutdown signal sent"); + } + Err(e) => { + println!(" ⚠ Warning: Failed to send shutdown signal: {:?}", e); + } + } + } + + // Remove VM from global list + match vm_list::remove_vm(vm_id) { + Some(_) => { + println!("✓ VM[{}] deleted successfully", vm_id); + } + None => { + println!("✗ Failed to remove VM[{}] from list", vm_id); + } + } +} + +#[cfg(feature = "fs")] +fn vm_list_simple() { + let vms = vm_list::get_vm_list(); + println!("ID NAME STATE VCPU MEMORY"); + println!("---- ----------- ------- ---- ------"); + for vm in vms { + let vm: vm_list::VMRef = vm; + let status = vm.status(); + let vcpu_num = vm.vcpu_num(); + let memory_size = vm.memory_size(); + + println!( + "{:<4} {:<11} {:<7} {:<4} {}", + usize::from(vm.id()), + vm.name(), + format!("{:?}", status), + vcpu_num, + format_memory_size(memory_size) + ); + } +} + +fn vm_list(cmd: &ParsedCommand) { + let binding = "table".to_string(); + let format = cmd.options.get("format").unwrap_or(&binding); + + let display_vms = vm_list::get_vm_list(); + + if display_vms.is_empty() { + println!("No virtual machines found."); + return; + } + + if format == "json" { + // JSON output + println!("{{"); + println!(" \"vms\": ["); + for (i, vm) in display_vms.iter().enumerate() { + let vm: &vm_list::VMRef = vm; + let status = vm.status(); + let total_memory = vm.memory_size(); + let vcpu_num = vm.vcpu_num(); + + println!(" {{"); + println!(" \"id\": {},", usize::from(vm.id())); + println!(" \"name\": \"{}\",", vm.name()); + println!(" \"state\": {:?},", status); + println!(" \"vcpu\": {},", vcpu_num); + println!(" \"memory\": \"{}\"", format_memory_size(total_memory)); + + if i < display_vms.len() - 1 { + println!(" }},"); + } else { + println!(" }}"); + } + } + println!(" ]"); + println!("}}"); + } else { + // Table output (default) + println!( + "{:<6} {:<15} {:<12} {:<10} {:<10}", + "VM ID", "NAME", "STATUS", "VCPU", "MEMORY" + ); + println!( + "{:-<6} {:-<15} {:-<12} {:-<10} {:-<10}", + "", "", "", "", "" + ); + + for vm in display_vms { + let vm: vm_list::VMRef = vm; + let status = vm.status(); + let total_memory = vm.memory_size(); + let vcpu_num = vm.vcpu_num(); + + println!( + "{:<6} {:<15} {:<12} {:<10} {}", + usize::from(vm.id()), + vm.name(), + format!("{:?}", status), + vcpu_num, + format_memory_size(total_memory) + ); + } + } +} + +fn vm_show(cmd: &ParsedCommand) { + let args = &cmd.positional_args; + + if args.is_empty() { + println!("Error: No VM specified"); + println!("Usage: vm show "); + println!(); + println!("Use 'vm list' to see all VMs"); + return; + } + + // Show specific VM details + let vm_name: &String = &args[0]; + if let Ok(vm_id) = vm_name.parse::() { + show_vm_details(vm_id); + } else { + println!("Error: Invalid VM ID: {}", vm_name); + } +} + +/// Show VM information +fn show_vm_details(vm_id: usize) { + let vm: vm_list::VMRef = match vm_list::get_vm_by_id(vm_id) { + Some(vm) => vm, + None => { + println!("✗ VM[{}] not found", vm_id); + return; + } + }; + + let status = vm.status(); + + println!("=== VM Details: {} ===", vm_id); + println!(); + + // Basic Information + println!(" VM ID: {}", usize::from(vm.id())); + println!(" Name: {}", vm.name()); + println!(" Status: {:?}", status); + println!(" VCPUs: {}", vm.vcpu_num()); + println!(" Memory: {}", format_memory_size(vm.memory_size())); + + // Add state-specific information + match status { + VMStatus::Inited => { + println!(); + println!(" ℹ VM is ready. Use 'vm start {}' to boot.", vm_id); + } + VMStatus::Running => { + println!(); + println!(" ℹ VM is running."); + } + VMStatus::Stopped => { + println!(); + println!(" ℹ VM is stopped."); + } + _ => {} + } +} + +// ============================================================================ +// Command Registration +// ============================================================================ + +/// Build the VM command tree and register it. +pub fn register_vm_commands(tree: &mut BTreeMap) { + #[cfg(feature = "fs")] + let create_cmd = CommandNode::new("Create a new virtual machine") + .with_handler(vm_create) + .with_usage("vm create [OPTIONS] ...") + .with_option( + OptionDef::new("name", "Virtual machine name") + .with_short('n') + .with_long("name"), + ) + .with_option( + OptionDef::new("cpu", "Number of CPU cores") + .with_short('c') + .with_long("cpu"), + ) + .with_option( + OptionDef::new("memory", "Amount of memory") + .with_short('m') + .with_long("memory"), + ) + .with_flag( + FlagDef::new("force", "Force creation without confirmation") + .with_short('f') + .with_long("force"), + ); + + #[cfg(feature = "fs")] + let start_cmd = CommandNode::new("Start a virtual machine") + .with_handler(vm_start) + .with_usage("vm start [OPTIONS] [VM_ID...]") + .with_flag( + FlagDef::new("detach", "Start in background") + .with_short('d') + .with_long("detach"), + ) + .with_flag( + FlagDef::new("console", "Attach to console") + .with_short('c') + .with_long("console"), + ); + + let status_cmd = CommandNode::new("Stop a virtual machine") + .with_handler(vm_status) + .with_usage("vm stop [OPTIONS] ..."); + + let stop_cmd = CommandNode::new("Stop a virtual machine") + .with_handler(vm_stop) + .with_usage("vm stop [OPTIONS] ...") + .with_flag( + FlagDef::new("force", "Force stop") + .with_short('f') + .with_long("force"), + ) + .with_flag( + FlagDef::new("graceful", "Graceful shutdown") + .with_short('g') + .with_long("graceful"), + ); + + let delete_cmd = CommandNode::new("Delete a virtual machine") + .with_handler(vm_delete) + .with_usage("vm delete [OPTIONS] ") + .with_flag( + FlagDef::new("force", "Force delete without stopping VM first") + .with_short('f') + .with_long("force"), + ); + + let list_cmd = CommandNode::new("Show virtual machine lists") + .with_handler(vm_list) + .with_usage("vm list [OPTIONS]") + .with_flag( + FlagDef::new("all", "Show all VMs including stopped ones") + .with_short('a') + .with_long("all"), + ) + .with_option(OptionDef::new("format", "Output format (table, json)").with_long("format")); + + let show_cmd = CommandNode::new("Show detailed VM information") + .with_handler(vm_show) + .with_usage("vm show [OPTIONS] ") + .with_flag( + FlagDef::new("full", "Show full detailed information") + .with_short('f') + .with_long("full"), + ) + .with_flag( + FlagDef::new("config", "Show configuration details") + .with_short('c') + .with_long("config"), + ) + .with_flag( + FlagDef::new("stats", "Show device statistics") + .with_short('s') + .with_long("stats"), + ); + + // main VM command + let mut vm_node = CommandNode::new("Virtual machine management") + .with_handler(vm_help) + .with_usage("vm [options] [args...]") + .add_subcommand( + "help", + CommandNode::new("Show VM help").with_handler(vm_help), + ); + + #[cfg(feature = "fs")] + { + vm_node = vm_node + .add_subcommand("create", create_cmd) + .add_subcommand("start", start_cmd); + } + + vm_node = vm_node + .add_subcommand("status", status_cmd) + .add_subcommand("stop", stop_cmd) + .add_subcommand("delete", delete_cmd) + .add_subcommand("list", list_cmd) + .add_subcommand("show", show_cmd); + + tree.insert("vm".to_string(), vm_node); +} diff --git a/kernel/src/shell/completion.rs b/kernel/src/shell/completion.rs new file mode 100644 index 00000000..06d981bb --- /dev/null +++ b/kernel/src/shell/completion.rs @@ -0,0 +1,290 @@ +//! Tab completion module for file and directory names +//! +//! Provides intelligent filename and directory name completion +//! when the user presses the TAB key. + +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +/// Completion result containing possible matches +pub struct CompletionResult { + /// The common prefix of all matches + pub prefix: String, + /// List of all possible completions + pub matches: Vec, + /// The text that should be inserted (suffix to add) + pub insert_text: String, +} + +impl CompletionResult { + /// Create a new completion result + pub fn new(prefix: String, matches: Vec) -> Self { + let insert_text = if matches.len() == 1 { + // For single match, insert the full match + matches[0].clone() + } else { + // For multiple matches, insert the common prefix + prefix.clone() + }; + Self { prefix, matches, insert_text } + } + + /// Check if there's exactly one match + pub fn is_unique(&self) -> bool { + self.matches.len() == 1 + } + + /// Check if there are no matches + pub fn is_empty(&self) -> bool { + self.matches.is_empty() + } +} + +/// Perform tab completion for the given input line and cursor position +/// +/// # Arguments +/// * `line` - The current input line +/// * `cursor_pos` - The current cursor position +/// +/// # Returns +/// * `None` if no completion is possible +/// * `Some(CompletionResult)` with completion suggestions +pub fn complete(line: &str, cursor_pos: usize) -> Option { + // Find the word being completed (word under cursor) + let word_start = find_word_start(line, cursor_pos); + let word_to_complete = &line[word_start..cursor_pos]; + + // Check if we should complete commands or filenames + // If we're at the beginning of the line or after a space, complete commands + // Otherwise, complete filenames + if is_at_command_position(line, word_start) { + complete_command(word_to_complete) + } else { + // For filename completion, we need to extract the path prefix and filename part + // Returns a CompletionResult where insert_text is the full path (path_prefix + filename) + complete_filename(word_to_complete) + } +} + +/// Find the start position of the word under the cursor +pub fn find_word_start(line: &str, cursor_pos: usize) -> usize { + let bytes = line.as_bytes(); + let mut pos = cursor_pos.saturating_sub(1); + + while pos > 0 && !is_whitespace(bytes[pos]) { + pos -= 1; + } + + if is_whitespace(bytes[pos]) { + pos + 1 + } else { + pos + } +} + +/// Check if a byte is a whitespace character +fn is_whitespace(b: u8) -> bool { + b == b' ' || b == b'\t' +} + +/// Check if we're at a position where commands should be completed +/// (either at the start of line or after a pipe) +fn is_at_command_position(line: &str, word_start: usize) -> bool { + let before_word = &line[..word_start]; + let trimmed = before_word.trim(); + + // At the start of the line + if trimmed.is_empty() { + return true; + } + + // After a pipe (simple check) + if trimmed.ends_with('|') { + return true; + } + + false +} + +/// Complete command names +#[cfg(feature = "fs")] +fn complete_command(partial: &str) -> Option { + use super::commands::COMMAND_TREE; + + let partial_lower = partial.to_lowercase(); + let mut matches: Vec = (*COMMAND_TREE) + .keys() + .filter(|cmd: &&String| cmd.starts_with(&partial_lower)) + .cloned() + .collect(); + + if matches.is_empty() { + return None; + } + + matches.sort(); + + let common_prefix = find_common_prefix(&matches); + Some(CompletionResult::new(common_prefix, matches)) +} + +/// Complete command names (no filesystem feature) +#[cfg(not(feature = "fs"))] +fn complete_command(partial: &str) -> Option { + use super::commands::COMMAND_TREE; + + let partial_lower = partial.to_lowercase(); + let mut matches: Vec = (*COMMAND_TREE) + .keys() + .filter(|cmd: &&String| cmd.starts_with(&partial_lower)) + .cloned() + .collect(); + + if matches.is_empty() { + return None; + } + + matches.sort(); + + let common_prefix = find_common_prefix(&matches); + Some(CompletionResult::new(common_prefix, matches)) +} + +/// Complete file and directory names +#[cfg(feature = "fs")] +fn complete_filename(partial: &str) -> Option { + use alloc::string::ToString; + use axstd::fs; + + // Split into directory path and file prefix + let (dir_path, file_prefix, path_prefix) = if let Some(last_slash) = partial.rfind('/') { + // If partial ends with '/', complete everything in that directory + if last_slash == partial.len() - 1 { + // dir_path is everything up to and including the last slash + (&partial[..], "", partial.to_string()) + } else { + // dir_path is everything up to and including the last slash + // file_prefix is everything after the last slash + (&partial[..=last_slash], &partial[last_slash + 1..], partial[..=last_slash].to_string()) + } + } else { + (".", partial, String::new()) + }; + + // Try to read the directory + let entries = match fs::read_dir(dir_path) { + Ok(entries) => entries, + Err(_) => return None, + }; + + let mut matches: Vec = Vec::new(); + + for entry in entries.flatten() { + let name = entry.file_name(); + + // Skip hidden files (starting with '.') unless explicitly requested + if !file_prefix.starts_with('.') && name.starts_with('.') { + continue; + } + + if name.starts_with(file_prefix) { + // Check if it's a directory by file type + let file_type = entry.file_type(); + + // Add '/' suffix for directories + let completed_name = if matches!(file_type, axstd::fs::FileType::Dir) { + format!("{name}/") + } else { + name.clone() + }; + + // For matches, we need to include the path prefix + // e.g., if partial is "/gu" and we find "guest", match should be "/guest/" + let full_match = if !path_prefix.is_empty() && path_prefix != "/" { + format!("{}{}", path_prefix, completed_name) + } else if path_prefix == "/" { + format!("/{}", completed_name) + } else { + completed_name.clone() + }; + + matches.push(full_match); + } + } + + if matches.is_empty() { + return None; + } + + matches.sort(); + + let common_prefix = find_common_prefix(&matches); + Some(CompletionResult::new(common_prefix, matches)) +} + +/// Complete file and directory names (no filesystem support) +#[cfg(not(feature = "fs"))] +fn complete_filename(_partial: &str) -> Option { + // No filesystem support, no filename completion + None +} + +/// Find the common prefix among all strings +fn find_common_prefix(strings: &[String]) -> String { + if strings.is_empty() { + return String::new(); + } + + if strings.len() == 1 { + return strings[0].clone(); + } + + let first = &strings[0]; + let mut end = first.len(); + + for s in &strings[1..] { + let mut new_end = 0; + for (i, (a, b)) in first.bytes().zip(s.bytes()).enumerate() { + if a == b { + new_end = i + 1; + } else { + break; + } + } + end = end.min(new_end); + if end == 0 { + break; + } + } + + first[..end].to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_common_prefix() { + assert_eq!(find_common_prefix(&[]), ""); + assert_eq!(find_common_prefix(&["test".into()]), "test"); + assert_eq!(find_common_prefix(&["test".into(), "testing".into()]), "test"); + assert_eq!(find_common_prefix(&["foo".into(), "bar".into()]), ""); + assert_eq!(find_common_prefix(&["file1.txt".into(), "file2.txt".into()]), "file"); + } + + #[test] + fn test_find_word_start() { + assert_eq!(find_word_start("ls test", 6), 3); + assert_eq!(find_word_start("ls test file", 6), 3); + assert_eq!(find_word_start("ls", 2), 0); + assert_eq!(find_word_start("ls test", 7), 5); + } + + #[test] + fn test_is_at_command_position() { + assert!(is_at_command_position("ls", 0)); + assert!(is_at_command_position("| ls", 2)); + assert!(!is_at_command_position("ls file", 3)); + } +} diff --git a/kernel/src/shell/mod.rs b/kernel/src/shell/mod.rs index 89850240..edfa6d03 100644 --- a/kernel/src/shell/mod.rs +++ b/kernel/src/shell/mod.rs @@ -1,225 +1,34 @@ -mod command; - -use std::io::prelude::*; -use std::println; -use std::string::ToString; - -use crate::shell::command::{ - CommandHistory, clear_line_and_redraw, handle_builtin_commands, print_prompt, run_cmd_bytes, +//! AxVisor Shell +//! +//! A command-line shell for AxVisor with support for: +//! - Command history and navigation +//! - File system operations (when fs feature is enabled) +//! - Virtual machine management +//! +//! # Example +//! +//! ```no_run +//! use axvisor_shell::console_init; +//! +//! // Start the shell in blocking mode +//! console_init(); +//! ``` + +#![no_std] + +mod completion; +mod parser; +mod shell; + +mod commands; + +// Re-export shell types and functions +pub use shell::{Shell, console_init, console_init_non_blocking}; + +// Re-export parser types for external use +pub use parser::{ + CommandHistory, CommandNode, CommandParser, FlagDef, OptionDef, ParseError, ParsedCommand, }; -const LF: u8 = b'\n'; -const CR: u8 = b'\r'; -const DL: u8 = b'\x7f'; -const BS: u8 = b'\x08'; -const ESC: u8 = 0x1b; // ESC key - -const MAX_LINE_LEN: usize = 256; - -// Initialize the console shell. -pub fn console_init() { - let mut stdin = std::io::stdin(); - let mut stdout = std::io::stdout(); - let mut history = CommandHistory::new(100); - - let mut buf = [0; MAX_LINE_LEN]; - let mut cursor = 0; // cursor position in buffer - let mut line_len = 0; // actual length of current line - - enum InputState { - Normal, - Escape, - EscapeSeq, - } - - let mut input_state = InputState::Normal; - - println!("Welcome to AxVisor Shell!"); - println!("Type 'help' to see available commands"); - println!("Use UP/DOWN arrows to navigate command history"); - #[cfg(not(feature = "fs"))] - println!("Note: Running with limited features (filesystem support disabled)."); - println!(); - - print_prompt(); - - loop { - let mut temp_buf = [0u8; 1]; - - let ch = match stdin.read(&mut temp_buf) { - Ok(1) => temp_buf[0], - _ => { - continue; - } - }; - - match input_state { - InputState::Normal => { - match ch { - CR | LF => { - println!(); - if line_len > 0 { - let cmd_str = std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - - // Add to history - history.add_command(cmd_str.to_string()); - - // Execute command - if !handle_builtin_commands(cmd_str) { - run_cmd_bytes(&buf[..line_len]); - } - - // reset buffer - buf[..line_len].fill(0); - cursor = 0; - line_len = 0; - } - print_prompt(); - } - BS | DL => { - // backspace: delete character before cursor / DEL key: delete character at cursor - if cursor > 0 { - // move characters after cursor forward - for i in cursor..line_len { - buf[i - 1] = buf[i]; - } - cursor -= 1; - line_len -= 1; - if line_len < buf.len() { - buf[line_len] = 0; - } - - let current_content = - std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); - } - } - ESC => { - input_state = InputState::Escape; - } - 0..=31 => { - // ignore other control characters - } - c => { - // insert character - if line_len < MAX_LINE_LEN - 1 { - // move characters after cursor backward to make space for new character - for i in (cursor..line_len).rev() { - buf[i + 1] = buf[i]; - } - buf[cursor] = c; - cursor += 1; - line_len += 1; - - let current_content = - std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); - } - } - } - } - InputState::Escape => match ch { - b'[' => { - input_state = InputState::EscapeSeq; - } - _ => { - input_state = InputState::Normal; - } - }, - InputState::EscapeSeq => { - match ch { - b'A' => { - // UP arrow - previous command - if let Some(prev_cmd) = history.previous() { - // clear current buffer - buf[..line_len].fill(0); - - let cmd_bytes = prev_cmd.as_bytes(); - let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); - buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); - cursor = copy_len; - line_len = copy_len; - #[cfg(feature = "fs")] - let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, prev_cmd, cursor); - } - input_state = InputState::Normal; - } - b'B' => { - // DOWN arrow - next command - match history.next() { - Some(next_cmd) => { - // clear current buffer - buf[..line_len].fill(0); - - let cmd_bytes = next_cmd.as_bytes(); - let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); - buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); - cursor = copy_len; - line_len = copy_len; - - #[cfg(feature = "fs")] - let prompt = - format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, next_cmd, cursor); - } - None => { - // clear current line - buf[..line_len].fill(0); - cursor = 0; - line_len = 0; - #[cfg(feature = "fs")] - let prompt = - format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); - clear_line_and_redraw(&mut stdout, &prompt, "", cursor); - } - } - input_state = InputState::Normal; - } - b'C' => { - // RIGHT arrow - move cursor right - if cursor < line_len { - cursor += 1; - stdout.write_all(b"\x1b[C").ok(); - stdout.flush().ok(); - } - input_state = InputState::Normal; - } - b'D' => { - // LEFT arrow - move cursor left - if cursor > 0 { - cursor -= 1; - stdout.write_all(b"\x1b[D").ok(); - stdout.flush().ok(); - } - input_state = InputState::Normal; - } - b'3' => { - // check if this is Delete key sequence (ESC[3~) - // need to read next character to confirm - input_state = InputState::Normal; - // can add additional state to handle complete Delete sequence - } - _ => { - // ignore other escape sequences - input_state = InputState::Normal; - } - } - } - } - } -} +// Re-export commands module +pub use commands::{COMMAND_TREE, execute_command, show_available_commands, show_help}; diff --git a/kernel/src/shell/command/history.rs b/kernel/src/shell/parser/history.rs similarity index 80% rename from kernel/src/shell/command/history.rs rename to kernel/src/shell/parser/history.rs index 9c13fc83..db3d76bc 100644 --- a/kernel/src/shell/command/history.rs +++ b/kernel/src/shell/parser/history.rs @@ -1,6 +1,11 @@ +//! Command history management +//! +//! Provides functionality to store and navigate through command history. + use std::io::prelude::*; use std::{string::String, vec::Vec}; +/// Command history storage and navigation pub struct CommandHistory { history: Vec, current_index: usize, @@ -8,6 +13,7 @@ pub struct CommandHistory { } impl CommandHistory { + /// Create a new command history with the given maximum size pub fn new(max_size: usize) -> Self { Self { history: Vec::new(), @@ -16,6 +22,7 @@ impl CommandHistory { } } + /// Add a command to the history pub fn add_command(&mut self, cmd: String) { if !cmd.trim().is_empty() && self.history.last() != Some(&cmd) { if self.history.len() >= self.max_size { @@ -26,6 +33,7 @@ impl CommandHistory { self.current_index = self.history.len(); } + /// Get the previous command in history #[allow(dead_code)] pub fn previous(&mut self) -> Option<&String> { if self.current_index > 0 { @@ -36,6 +44,7 @@ impl CommandHistory { } } + /// Get the next command in history #[allow(dead_code)] pub fn next(&mut self) -> Option<&String> { if self.current_index < self.history.len() { @@ -51,6 +60,7 @@ impl CommandHistory { } } +/// Clear the current line and redraw it with the given content #[allow(unused_must_use)] pub fn clear_line_and_redraw( stdout: &mut dyn Write, diff --git a/kernel/src/shell/parser/mod.rs b/kernel/src/shell/parser/mod.rs new file mode 100644 index 00000000..a0cb47c0 --- /dev/null +++ b/kernel/src/shell/parser/mod.rs @@ -0,0 +1,11 @@ +//! Command parsing module +//! +//! Provides functionality to parse and structure commands. + +mod history; +mod node; +mod parser; + +pub use history::{CommandHistory, clear_line_and_redraw}; +pub use node::{CommandNode, FlagDef, OptionDef, ParseError, ParsedCommand}; +pub use parser::CommandParser; diff --git a/kernel/src/shell/parser/node.rs b/kernel/src/shell/parser/node.rs new file mode 100644 index 00000000..8b7df812 --- /dev/null +++ b/kernel/src/shell/parser/node.rs @@ -0,0 +1,172 @@ +//! Command node definitions for the command tree +//! +//! Defines the structures used to build the command tree hierarchy. + +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::vec::Vec; + +/// A node in the command tree +#[derive(Debug, Clone)] +pub struct CommandNode { + handler: Option, + pub subcommands: BTreeMap, + pub description: &'static str, + pub usage: Option<&'static str>, + #[allow(dead_code)] + pub log_level: log::LevelFilter, + pub options: Vec, + pub flags: Vec, +} + +impl CommandNode { + /// Create a new command node with a description + pub fn new(description: &'static str) -> Self { + Self { + handler: None, + subcommands: BTreeMap::new(), + description, + usage: None, + log_level: log::LevelFilter::Off, + options: Vec::new(), + flags: Vec::new(), + } + } + + /// Set the handler function for this command + pub fn with_handler(mut self, handler: fn(&ParsedCommand)) -> Self { + self.handler = Some(handler); + self + } + + /// Set the usage string for this command + pub fn with_usage(mut self, usage: &'static str) -> Self { + self.usage = Some(usage); + self + } + + /// Set the log level for this command + #[allow(dead_code)] + pub fn with_log_level(mut self, level: log::LevelFilter) -> Self { + self.log_level = level; + self + } + + /// Add an option to this command + pub fn with_option(mut self, option: OptionDef) -> Self { + self.options.push(option); + self + } + + /// Add a flag to this command + pub fn with_flag(mut self, flag: FlagDef) -> Self { + self.flags.push(flag); + self + } + + /// Add a subcommand to this command + pub fn add_subcommand>(mut self, name: S, node: CommandNode) -> Self { + self.subcommands.insert(name.into(), node); + self + } + + /// Get the handler for this command + pub fn handler(&self) -> Option { + self.handler + } +} + +/// Definition of a command option (takes a value) +#[derive(Debug, Clone)] +pub struct OptionDef { + pub name: &'static str, + pub short: Option, + pub long: Option<&'static str>, + pub description: &'static str, + pub required: bool, +} + +impl OptionDef { + /// Create a new option definition + pub fn new(name: &'static str, description: &'static str) -> Self { + Self { + name, + short: None, + long: None, + description, + required: false, + } + } + + /// Set the short flag (e.g., -v) + #[allow(dead_code)] + pub fn with_short(mut self, short: char) -> Self { + self.short = Some(short); + self + } + + /// Set the long flag (e.g., --verbose) + pub fn with_long(mut self, long: &'static str) -> Self { + self.long = Some(long); + self + } + + /// Mark this option as required + #[allow(dead_code)] + pub fn required(mut self) -> Self { + self.required = true; + self + } +} + +/// Definition of a command flag (boolean) +#[derive(Debug, Clone)] +pub struct FlagDef { + pub name: &'static str, + pub short: Option, + pub long: Option<&'static str>, + pub description: &'static str, +} + +impl FlagDef { + /// Create a new flag definition + pub fn new(name: &'static str, description: &'static str) -> Self { + Self { + name, + short: None, + long: None, + description, + } + } + + /// Set the short flag (e.g., -v) + pub fn with_short(mut self, short: char) -> Self { + self.short = Some(short); + self + } + + /// Set the long flag (e.g., --verbose) + pub fn with_long(mut self, long: &'static str) -> Self { + self.long = Some(long); + self + } +} + +/// A parsed command with all arguments and options +#[derive(Debug, Clone)] +pub struct ParsedCommand { + pub command_path: Vec, + pub options: BTreeMap, + pub flags: BTreeMap, + pub positional_args: Vec, +} + +/// Errors that can occur during command parsing +#[derive(Debug)] +pub enum ParseError { + UnknownCommand(String), + UnknownOption(String), + MissingValue(String), + MissingRequiredOption(String), + NoHandler(String), +} diff --git a/kernel/src/shell/parser/parser.rs b/kernel/src/shell/parser/parser.rs new file mode 100644 index 00000000..5526db08 --- /dev/null +++ b/kernel/src/shell/parser/parser.rs @@ -0,0 +1,241 @@ +//! Command parser implementation +//! +//! Parses command strings into structured command objects. + +use super::node::{CommandNode, ParseError, ParsedCommand}; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; + +/// Command parser +pub struct CommandParser; + +impl CommandParser { + /// Parse a command string into a structured command + pub fn parse( + input: &str, + command_tree: &BTreeMap, + ) -> Result { + let tokens = Self::tokenize(input); + if tokens.is_empty() { + return Err(ParseError::UnknownCommand("empty command".to_string())); + } + + // Find the command path + let (command_path, command_node, remaining_tokens) = + Self::find_command(&tokens, command_tree)?; + + // Parse the arguments + let (options, flags, positional_args) = Self::parse_args(remaining_tokens, command_node)?; + + // Validate required options + Self::validate_required_options(command_node, &options)?; + + Ok(ParsedCommand { + command_path, + options, + flags, + positional_args, + }) + } + + /// Split input string into tokens, handling quotes and escapes + fn tokenize(input: &str) -> Vec { + let mut tokens = Vec::new(); + let mut current_token = String::new(); + let mut in_quotes = false; + let mut escape_next = false; + + for ch in input.chars() { + if escape_next { + current_token.push(ch); + escape_next = false; + } else if ch == '\\' { + escape_next = true; + } else if ch == '"' { + in_quotes = !in_quotes; + } else if ch.is_whitespace() && !in_quotes { + if !current_token.is_empty() { + tokens.push(current_token.clone()); + current_token.clear(); + } + } else { + current_token.push(ch); + } + } + + if !current_token.is_empty() { + tokens.push(current_token); + } + + tokens + } + + /// Find the command node for the given tokens + fn find_command<'a>( + tokens: &'a [String], + command_tree: &'a BTreeMap, + ) -> Result<(Vec, &'a CommandNode, &'a [String]), ParseError> { + let mut current_node = command_tree + .get(&tokens[0]) + .ok_or_else(|| ParseError::UnknownCommand(tokens[0].clone()))?; + + let mut command_path = vec![tokens[0].clone()]; + let mut token_index = 1; + + // Traverse to find the deepest command node + while token_index < tokens.len() { + if let Some(subcommand) = current_node.subcommands.get(&tokens[token_index]) { + current_node = subcommand; + command_path.push(tokens[token_index].clone()); + token_index += 1; + } else { + break; + } + } + + Ok((command_path, current_node, &tokens[token_index..])) + } + + /// Parse arguments into options, flags, and positional args + #[allow(clippy::type_complexity)] + fn parse_args( + tokens: &[String], + command_node: &CommandNode, + ) -> Result< + ( + BTreeMap, + BTreeMap, + Vec, + ), + ParseError, + > { + let mut options = BTreeMap::new(); + let mut flags = BTreeMap::new(); + let mut positional_args = Vec::new(); + let mut i = 0; + + while i < tokens.len() { + let token = &tokens[i]; + + if let Some(name) = token.strip_prefix("--") { + // Long options/flags + if let Some(eq_pos) = name.find('=') { + // --option=value format + let (opt_name, value) = name.split_at(eq_pos); + let value = &value[1..]; // Skip '=' + if Self::is_option(opt_name, command_node) { + options.insert(opt_name.to_string(), value.to_string()); + } else { + return Err(ParseError::UnknownOption(format!("--{opt_name}"))); + } + } else if Self::is_flag(name, command_node) { + flags.insert(name.to_string(), true); + } else if Self::is_option(name, command_node) { + // --option value format + if i + 1 >= tokens.len() { + return Err(ParseError::MissingValue(format!("--{name}"))); + } + options.insert(name.to_string(), tokens[i + 1].clone()); + i += 1; // Skip value + } else { + return Err(ParseError::UnknownOption(format!("--{name}"))); + } + } else if token.starts_with('-') && token.len() > 1 { + // Short options/flags + let chars: Vec = token[1..].chars().collect(); + for (j, &ch) in chars.iter().enumerate() { + if Self::is_short_flag(ch, command_node) { + flags.insert( + Self::get_flag_name_by_short(ch, command_node) + .unwrap() + .to_string(), + true, + ); + } else if Self::is_short_option(ch, command_node) { + let opt_name = Self::get_option_name_by_short(ch, command_node).unwrap(); + if j == chars.len() - 1 && i + 1 < tokens.len() { + // Last character and there is a next token as value + options.insert(opt_name.to_string(), tokens[i + 1].clone()); + i += 1; // Skip value + } else { + return Err(ParseError::MissingValue(format!("-{ch}"))); + } + } else { + return Err(ParseError::UnknownOption(format!("-{ch}"))); + } + } + } else { + // Positional arguments + positional_args.push(token.clone()); + } + i += 1; + } + + Ok((options, flags, positional_args)) + } + + fn is_option(name: &str, node: &CommandNode) -> bool { + node.options + .iter() + .any(|opt| (opt.long == Some(name)) || opt.name == name) + } + + fn is_flag(name: &str, node: &CommandNode) -> bool { + node.flags + .iter() + .any(|flag| (flag.long == Some(name)) || flag.name == name) + } + + fn is_short_option(ch: char, node: &CommandNode) -> bool { + node.options.iter().any(|opt| opt.short == Some(ch)) + } + + fn is_short_flag(ch: char, node: &CommandNode) -> bool { + node.flags.iter().any(|flag| flag.short == Some(ch)) + } + + fn get_option_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { + node.options + .iter() + .find(|opt| opt.short == Some(ch)) + .map(|opt| opt.name) + } + + fn get_flag_name_by_short(ch: char, node: &CommandNode) -> Option<&str> { + node.flags + .iter() + .find(|flag| flag.short == Some(ch)) + .map(|flag| flag.name) + } + + fn validate_required_options( + node: &CommandNode, + options: &BTreeMap, + ) -> Result<(), ParseError> { + for option in &node.options { + if option.required && !options.contains_key(option.name) { + return Err(ParseError::MissingRequiredOption(option.name.to_string())); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::node::{CommandNode, FlagDef}; + + #[test] + fn test_tokenize() { + let tokens = CommandParser::tokenize("hello world"); + assert_eq!(tokens, vec!["hello", "world"]); + + let tokens = CommandParser::tokenize("hello \"world test\""); + assert_eq!(tokens, vec!["hello", "world test"]); + + let tokens = CommandParser::tokenize("hello\\ world"); + assert_eq!(tokens, vec!["hello world"]); + } +} diff --git a/kernel/src/shell/shell.rs b/kernel/src/shell/shell.rs new file mode 100644 index 00000000..78112e7f --- /dev/null +++ b/kernel/src/shell/shell.rs @@ -0,0 +1,385 @@ +//! Shell core implementation +//! +//! Provides the main shell functionality including input handling, +//! command execution, and state management. + +use std::io::prelude::*; +use std::print; +use std::println; + +use alloc::string::{String, ToString}; + +use super::commands::{handle_builtin_commands, print_prompt, run_cmd_bytes}; +use super::completion; +use super::parser::{CommandHistory, clear_line_and_redraw}; + +const LF: u8 = b'\n'; +const CR: u8 = b'\r'; +const DL: u8 = b'\x7f'; +const BS: u8 = b'\x08'; +const TAB: u8 = b'\t'; // TAB key for completion +const ESC: u8 = 0x1b; // ESC key + +const MAX_LINE_LEN: usize = 256; + +/// Shell state that can be stored and restored +pub struct Shell { + stdin: std::io::Stdin, + stdout: std::io::Stdout, + history: CommandHistory, + buf: [u8; MAX_LINE_LEN], + cursor: usize, + line_len: usize, + input_state: InputState, + initialized: bool, +} + +/// Input state for handling escape sequences +#[derive(Clone, Copy)] +enum InputState { + Normal, + Escape, + EscapeSeq, +} + +impl Shell { + /// Create a new shell instance + pub fn new() -> Self { + Self { + stdin: std::io::stdin(), + stdout: std::io::stdout(), + history: CommandHistory::new(100), + buf: [0; MAX_LINE_LEN], + cursor: 0, + line_len: 0, + input_state: InputState::Normal, + initialized: false, + } + } + + /// Initialize the shell (print welcome message) + pub fn init(&mut self) { + if self.initialized { + return; + } + + println!("Welcome to AxVisor Shell!"); + println!("Type 'help' to see available commands"); + println!("Use UP/DOWN arrows to navigate command history"); + println!("Press TAB to autocomplete commands and filenames"); + println!("Note: Only ASCII characters are supported for input"); + #[cfg(not(feature = "fs"))] + println!("Note: Running with limited features (filesystem support disabled)."); + println!(); + + print_prompt(); + self.initialized = true; + } + + /// Process one character of input. + /// Returns true if a command was executed (for potential scheduling decisions). + pub fn process_char(&mut self) -> bool { + if !self.initialized { + self.init(); + } + + let mut temp_buf = [0u8; 1]; + + let ch = match self.stdin.read(&mut temp_buf) { + Ok(1) => temp_buf[0], + _ => return false, + }; + + self.process_input(ch) + } + + fn process_input(&mut self, ch: u8) -> bool { + let mut command_executed = false; + + match self.input_state { + InputState::Normal => match ch { + CR | LF => { + println!(); + if self.line_len > 0 { + let cmd_str = std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + + // Add to history + self.history.add_command(cmd_str.to_string()); + + // Execute command + if !handle_builtin_commands(cmd_str) { + run_cmd_bytes(&self.buf[..self.line_len]); + } + + command_executed = true; + + // reset buffer + self.buf[..self.line_len].fill(0); + self.cursor = 0; + self.line_len = 0; + } + print_prompt(); + } + BS | DL => { + // backspace: delete character before cursor / DEL key: delete character at cursor + if self.cursor > 0 { + // move characters after cursor forward + for i in self.cursor..self.line_len { + self.buf[i - 1] = self.buf[i]; + } + self.cursor -= 1; + self.line_len -= 1; + if self.line_len < self.buf.len() { + self.buf[self.line_len] = 0; + } + + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw( + &mut self.stdout, + &prompt, + current_content, + self.cursor, + ); + } + } + TAB => { + // Tab completion + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + + if let Some(result) = completion::complete(current_content, self.cursor) { + let result: &completion::CompletionResult = &result; + let is_unique = result.is_unique(); + let matches_count = result.matches.len(); + + // If there are multiple matches, always show all options first + if !is_unique && matches_count > 1 { + println!(); + // Strip the common prefix from matches for cleaner display + let display_prefix: &str = &result.prefix; + for (i, match_name) in result.matches.iter().enumerate() { + let match_name: &String = match_name; + let display_name: &str = if match_name.starts_with(display_prefix) { + &match_name[display_prefix.len()..] + } else { + match_name.as_str() + }; + print!("{} ", display_name); // Add explicit spacing + if (i + 1) % 3 == 0 { + println!(); + } + } + if result.matches.len() % 3 != 0 { + println!(); + } + print_prompt(); + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + print!("{}", current_content); + self.stdout.flush().ok(); + } else if is_unique && matches_count > 0 { + // Single match - insert the full match + let text_to_insert = &result.matches[0]; + let word_start = completion::find_word_start(current_content, self.cursor); + let current_word = ¤t_content[word_start..self.cursor]; + + // Calculate what we need to add (match minus what's already typed) + let to_add = if text_to_insert.starts_with(current_word) { + &text_to_insert[current_word.len()..] + } else { + text_to_insert + }; + + if !to_add.is_empty() { + let to_add_bytes = to_add.as_bytes(); + let insert_len = to_add_bytes.len().min(MAX_LINE_LEN - self.line_len - 1); + + if insert_len > 0 { + // Move existing characters to make space + for i in (self.cursor..self.line_len).rev() { + self.buf[i + insert_len] = self.buf[i]; + } + + // Insert completion + self.buf[self.cursor..self.cursor + insert_len] + .copy_from_slice(&to_add_bytes[..insert_len]); + + self.cursor += insert_len; + self.line_len += insert_len; + + // Redraw + let new_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, new_content, self.cursor); + } + } + } + } + } + ESC => { + self.input_state = InputState::Escape; + } + 0..=31 => { + // ignore other control characters (already handled: LF, CR, BS, DL, TAB, ESC) + } + c @ 32..=126 => { + // insert ASCII printable character + if self.line_len < MAX_LINE_LEN - 1 { + // move characters after cursor backward to make space for new character + for i in (self.cursor..self.line_len).rev() { + self.buf[i + 1] = self.buf[i]; + } + self.buf[self.cursor] = c; + self.cursor += 1; + self.line_len += 1; + + let current_content = + std::str::from_utf8(&self.buf[..self.line_len]).unwrap_or(""); + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw( + &mut self.stdout, + &prompt, + current_content, + self.cursor, + ); + } + } + _ => { + // Non-ASCII or DEL characters - ignore for now + // In a full implementation, we would handle multi-byte UTF-8 sequences + } + }, + InputState::Escape => match ch { + b'[' => { + self.input_state = InputState::EscapeSeq; + } + _ => { + self.input_state = InputState::Normal; + } + }, + InputState::EscapeSeq => match ch { + b'A' => { + // UP arrow - previous command + if let Some(prev_cmd) = self.history.previous() { + let prev_cmd: &String = prev_cmd; + // clear current buffer + self.buf[..self.line_len].fill(0); + + let cmd_bytes: &[u8] = prev_cmd.as_bytes(); + let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); + self.buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); + self.cursor = copy_len; + self.line_len = copy_len; + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, prev_cmd, self.cursor); + } + self.input_state = InputState::Normal; + } + b'B' => { + // DOWN arrow - next command + match self.history.next() { + Some(next_cmd) => { + let next_cmd: &String = next_cmd; + // clear current buffer + self.buf[..self.line_len].fill(0); + + let cmd_bytes: &[u8] = next_cmd.as_bytes(); + let copy_len = cmd_bytes.len().min(MAX_LINE_LEN - 1); + self.buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); + self.cursor = copy_len; + self.line_len = copy_len; + + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, next_cmd, self.cursor); + } + None => { + // clear current line + self.buf[..self.line_len].fill(0); + self.cursor = 0; + self.line_len = 0; + #[cfg(feature = "fs")] + let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); + #[cfg(not(feature = "fs"))] + let prompt = "axvisor:$ ".to_string(); + clear_line_and_redraw(&mut self.stdout, &prompt, "", self.cursor); + } + } + self.input_state = InputState::Normal; + } + b'C' => { + // RIGHT arrow - move cursor right + if self.cursor < self.line_len { + self.cursor += 1; + self.stdout.write_all(b"\x1b[C").ok(); + self.stdout.flush().ok(); + } + self.input_state = InputState::Normal; + } + b'D' => { + // LEFT arrow - move cursor left + if self.cursor > 0 { + self.cursor -= 1; + self.stdout.write_all(b"\x1b[D").ok(); + self.stdout.flush().ok(); + } + self.input_state = InputState::Normal; + } + b'3' => { + // check if this is Delete key sequence (ESC[3~) + // need to read next character to confirm + self.input_state = InputState::Normal; + } + _ => { + // ignore other escape sequences + self.input_state = InputState::Normal; + } + }, + } + + command_executed + } + + /// Run the shell as a blocking loop (original behavior) + pub fn run(&mut self) { + self.init(); + loop { + self.process_char(); + } + } +} + +impl Default for Shell { + fn default() -> Self { + Self::new() + } +} + +/// Initialize the console shell with blocking behavior (backward compatible). +pub fn console_init() { + let mut shell = Shell::new(); + shell.run(); +} + +/// Non-blocking initialization that returns a Shell instance. +/// This allows the caller to control when to process input. +pub fn console_init_non_blocking() -> Shell { + Shell::new() +} diff --git a/kernel/src/vmm/config.rs b/kernel/src/vmm/config.rs index c4673acd..fb565f57 100644 --- a/kernel/src/vmm/config.rs +++ b/kernel/src/vmm/config.rs @@ -1,5 +1,4 @@ -use std::string::ToString; - +use alloc::string::ToString; use alloc::vec::Vec; use axvm::{ AxVMConfig, CpuId, diff --git a/kernel/src/vmm/fdt/create.rs b/kernel/src/vmm/fdt/create.rs deleted file mode 100644 index 0c8b5c32..00000000 --- a/kernel/src/vmm/fdt/create.rs +++ /dev/null @@ -1,469 +0,0 @@ -use alloc::{ - string::{String, ToString}, - vec::Vec, -}; -use core::ptr::NonNull; - -use axaddrspace::GuestPhysAddr; -use axvm::{VMMemoryRegion, config::AxVMCrateConfig}; -use fdt_parser::{Fdt, Node}; -use memory_addr::MemoryAddr; -use vm_fdt::{FdtWriter, FdtWriterNode}; - -use crate::vmm::{VMRef, images::load_vm_image_from_memory}; - -// use crate::vmm::fdt::print::{print_fdt, print_guest_fdt}; -/// Generate guest FDT and return DTB data -/// -/// # Parameters -/// * `fdt` - Source FDT data -/// * `passthrough_device_names` - Passthrough device name list -/// * `crate_config` - VM creation configuration -/// -/// # Return Value -/// Returns the generated DTB data -pub fn crate_guest_fdt( - fdt: &Fdt, - passthrough_device_names: &[String], - crate_config: &AxVMCrateConfig, -) -> Vec { - let mut fdt_writer = FdtWriter::new().unwrap(); - // Track the level of the previously processed node for level change handling - let mut previous_node_level = 0; - // Maintain a stack of FDT nodes to correctly start and end nodes - let mut node_stack: Vec = Vec::new(); - let phys_cpu_ids = crate_config - .base - .phys_cpu_ids - .clone() - .expect("ERROR: phys_cpu_ids is None"); - - let all_nodes: Vec = fdt.all_nodes().collect(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = super::build_node_path(&all_nodes, index); - let node_action = determine_node_action(node, &node_path, passthrough_device_names); - - match node_action { - NodeAction::RootNode => { - node_stack.push(fdt_writer.begin_node("").unwrap()); - } - NodeAction::CpuNode => { - let need = need_cpu_node(&phys_cpu_ids, node, &node_path); - if need { - handle_node_level_change( - &mut fdt_writer, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - } else { - continue; - } - } - NodeAction::Skip => { - continue; - } - _ => { - trace!( - "Found exact passthrough device node: {}, path: {}", - node.name(), - node_path - ); - handle_node_level_change( - &mut fdt_writer, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(fdt_writer.begin_node(node.name()).unwrap()); - } - } - - previous_node_level = node.level; - - // Copy all properties of the node - for prop in node.propertys() { - fdt_writer.property(prop.name, prop.raw_value()).unwrap(); - } - } - - // End all unclosed nodes - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - fdt_writer.end_node(node).unwrap(); - } - assert_eq!(previous_node_level, 0); - - fdt_writer.finish().unwrap() -} - -/// Node processing action enumeration -enum NodeAction { - /// Skip node, not included in guest FDT - Skip, - /// Root node - RootNode, - /// CPU node - CpuNode, - /// Include node as passthrough device node - IncludeAsPassthroughDevice, - /// Include node as child node of passthrough device - IncludeAsChildNode, - /// Include node as ancestor node of passthrough device - IncludeAsAncestorNode, -} - -/// Determine node processing action -fn determine_node_action( - node: &Node, - node_path: &str, - passthrough_device_names: &[String], -) -> NodeAction { - if node.name() == "/" { - // Special handling for root node - NodeAction::RootNode - } else if node.name().starts_with("memory") { - // Skip memory nodes, will add them later - NodeAction::Skip - } else if node_path.starts_with("/cpus") { - NodeAction::CpuNode - } else if passthrough_device_names.contains(&node_path.to_string()) { - // Fully matched passthrough device node - NodeAction::IncludeAsPassthroughDevice - } - // Check if the node is a descendant of a passthrough device (by path inclusion and level validation) - else if is_descendant_of_passthrough_device(node_path, node.level, passthrough_device_names) { - NodeAction::IncludeAsChildNode - } - // Check if the node is an ancestor of a passthrough device (by path inclusion and level validation) - else if is_ancestor_of_passthrough_device(node_path, passthrough_device_names) { - NodeAction::IncludeAsAncestorNode - } else { - NodeAction::Skip - } -} - -/// Determine if node is a descendant of passthrough device -/// When node path contains a path from passthrough_device_names and is longer than it, it is its descendant node -/// Also use node_level as validation condition -fn is_descendant_of_passthrough_device( - node_path: &str, - node_level: usize, - passthrough_device_names: &[String], -) -> bool { - for passthrough_path in passthrough_device_names { - // Check if the current node is a descendant of a passthrough device - if node_path.starts_with(passthrough_path) && node_path.len() > passthrough_path.len() { - // Ensure it is a true descendant path (separated by /) - if passthrough_path == "/" || node_path.chars().nth(passthrough_path.len()) == Some('/') - { - // Use level relationship for validation: the level of a descendant node should be higher than its parent - // Note: The level of the root node is 1, its direct child node level is 2, and so on - let expected_parent_level = passthrough_path.matches('/').count(); - let current_node_level = node_level; - - // If passthrough_path is the root node "/", then its child node level should be 2 - // Otherwise, the child node level should be higher than the parent node level - if (passthrough_path == "/" && current_node_level >= 2) - || (passthrough_path != "/" && current_node_level > expected_parent_level) - { - return true; - } - } - } - } - false -} - -/// Handle node level changes to ensure correct FDT structure -fn handle_node_level_change( - fdt_writer: &mut FdtWriter, - node_stack: &mut Vec, - current_level: usize, - previous_level: usize, -) { - if current_level <= previous_level { - for _ in current_level..=previous_level { - if let Some(end_node) = node_stack.pop() { - fdt_writer.end_node(end_node).unwrap(); - } - } - } -} - -/// Determine if node is an ancestor of passthrough device -fn is_ancestor_of_passthrough_device(node_path: &str, passthrough_device_names: &[String]) -> bool { - for passthrough_path in passthrough_device_names { - // Check if the current node is an ancestor of a passthrough device - if passthrough_path.starts_with(node_path) && passthrough_path.len() > node_path.len() { - // Ensure it is a true ancestor path (separated by /) - let next_char = passthrough_path.chars().nth(node_path.len()).unwrap_or(' '); - if next_char == '/' || node_path == "/" { - return true; - } - } - } - false -} - -/// Determine if CPU node is needed -fn need_cpu_node(phys_cpu_ids: &[usize], node: &Node, node_path: &str) -> bool { - let mut should_include_node = false; - - if !node_path.starts_with("/cpus/cpu@") { - should_include_node = true; - } else if let Some(mut cpu_reg) = node.reg() - && let Some(reg_entry) = cpu_reg.next() - { - let cpu_address = reg_entry.address as usize; - debug!( - "Checking CPU node {} with address 0x{:x}", - node.name(), - cpu_address - ); - // Check if this CPU address is in the configured phys_cpu_ids - if phys_cpu_ids.contains(&cpu_address) { - should_include_node = true; - debug!( - "CPU node {} with address 0x{:x} is in phys_cpu_ids, including in guest FDT", - node.name(), - cpu_address - ); - } else { - debug!( - "CPU node {} with address 0x{:x} is NOT in phys_cpu_ids, skipping", - node.name(), - cpu_address - ); - } - } - should_include_node -} - -/// Add memory node -fn add_memory_node(new_memory: &[VMMemoryRegion], new_fdt: &mut FdtWriter) { - let mut new_value: Vec = Vec::new(); - for mem in new_memory { - let gpa = mem.gpa.as_usize() as u64; - let size = mem.size() as u64; - new_value.push((gpa >> 32) as u32); - new_value.push((gpa & 0xFFFFFFFF) as u32); - new_value.push((size >> 32) as u32); - new_value.push((size & 0xFFFFFFFF) as u32); - } - info!("Adding memory node with value: 0x{new_value:x?}"); - new_fdt - .property_array_u32("reg", new_value.as_ref()) - .unwrap(); - new_fdt.property_string("device_type", "memory").unwrap(); -} - -pub fn update_fdt(fdt_src: NonNull, dtb_size: usize, vm: VMRef) { - let mut new_fdt = FdtWriter::new().unwrap(); - let mut previous_node_level = 0; - let mut node_stack: Vec = Vec::new(); - - let fdt_bytes = unsafe { core::slice::from_raw_parts(fdt_src.as_ptr(), dtb_size) }; - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - - for node in fdt.all_nodes() { - if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); - } else if node.name().starts_with("memory") { - // Skip memory nodes, will add them later - continue; - } else { - handle_node_level_change( - &mut new_fdt, - &mut node_stack, - node.level, - previous_node_level, - ); - // Start new node - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); - } - - previous_node_level = node.level; - - if node.name() == "chosen" { - for prop in node.propertys() { - if prop.name.starts_with("linux,initrd-") { - info!( - "Skipping property: {}, belonging to node: {}", - prop.name, - node.name() - ); - } else if prop.name == "bootargs" { - let bootargs_str = prop.str(); - let modified_bootargs = bootargs_str.replace(" ro ", " rw "); - - if modified_bootargs != bootargs_str { - info!( - "Modifying bootargs: {} -> {}", - bootargs_str, modified_bootargs - ); - } - - new_fdt - .property_string(prop.name, &modified_bootargs) - .unwrap(); - } else { - debug!( - "Find property: {}, belonging to node: {}", - prop.name, - node.name() - ); - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - } - } else { - for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - } - } - - // End all unclosed nodes, and add memory nodes at appropriate positions - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - new_fdt.end_node(node).unwrap(); - - // add memory node - if previous_node_level == 1 { - let memory_regions = vm.memory_regions(); - debug!("Adding memory node with regions: {memory_regions:?}"); - let memory_node = new_fdt.begin_node("memory").unwrap(); - add_memory_node(&memory_regions, &mut new_fdt); - new_fdt.end_node(memory_node).unwrap(); - } - } - - assert_eq!(previous_node_level, 0); - - info!("Updating FDT memory successfully"); - - let new_fdt_bytes = new_fdt.finish().unwrap(); - - // crate::vmm::fdt::print::print_guest_fdt(new_fdt_bytes.as_slice()); - let vm_clone = vm.clone(); - let dest_addr = calculate_dtb_load_addr(vm, new_fdt_bytes.len()); - info!( - "New FDT will be loaded at {:x}, size: 0x{:x}", - dest_addr, - new_fdt_bytes.len() - ); - // Load the updated FDT into VM - load_vm_image_from_memory(&new_fdt_bytes, dest_addr, vm_clone) - .expect("Failed to load VM images"); -} - -fn calculate_dtb_load_addr(vm: VMRef, fdt_size: usize) -> GuestPhysAddr { - const MB: usize = 1024 * 1024; - - // Get main memory from VM memory regions outside the closure - let main_memory = vm - .memory_regions() - .first() - .cloned() - .expect("VM must have at least one memory region"); - - vm.with_config(|config| { - let dtb_addr = if let Some(addr) = config.image_config.dtb_load_gpa - && !main_memory.is_identical() - { - // If dtb_load_gpa is already set, use the original value - addr - } else { - // If dtb_load_gpa is None, calculate based on memory size and FDT size - let main_memory_size = main_memory.size().min(512 * MB); - let addr = (main_memory.gpa + main_memory_size - fdt_size).align_down(2 * MB); - if fdt_size > main_memory_size { - error!("DTB size is larger than available memory"); - } - addr - }; - config.image_config.dtb_load_gpa = Some(dtb_addr); - dtb_addr - }) -} - -pub fn update_cpu_node(fdt: &Fdt, host_fdt: &Fdt, crate_config: &AxVMCrateConfig) -> Vec { - let mut new_fdt = FdtWriter::new().unwrap(); - let mut previous_node_level = 0; - let mut node_stack: Vec = Vec::new(); - let phys_cpu_ids = crate_config - .base - .phys_cpu_ids - .clone() - .expect("ERROR: phys_cpu_ids is None"); - - // Collect all nodes from both FDTs - let fdt_all_nodes: Vec = fdt.all_nodes().collect(); - let host_fdt_all_nodes: Vec = host_fdt.all_nodes().collect(); - - for (index, node) in fdt_all_nodes.iter().enumerate() { - let node_path = super::build_node_path(&fdt_all_nodes, index); - - if node.name() == "/" { - node_stack.push(new_fdt.begin_node("").unwrap()); - } else if node_path.starts_with("/cpus") { - // Skip CPU nodes from fdt, we'll process them from host_fdt later - continue; - } else { - // For all other nodes, include them from fdt as-is without filtering - handle_node_level_change( - &mut new_fdt, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); - } - - previous_node_level = node.level; - - // Copy all properties of the node (for non-CPU nodes) - for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - } - - // Process all CPU nodes from host_fdt - for (index, node) in host_fdt_all_nodes.iter().enumerate() { - let node_path = super::build_node_path(&host_fdt_all_nodes, index); - - if node_path.starts_with("/cpus") { - // For CPU nodes, apply filtering based on host_fdt nodes - let need = need_cpu_node(&phys_cpu_ids, node, &node_path); - if need { - handle_node_level_change( - &mut new_fdt, - &mut node_stack, - node.level, - previous_node_level, - ); - node_stack.push(new_fdt.begin_node(node.name()).unwrap()); - - // Copy properties from host CPU node - for prop in node.propertys() { - new_fdt.property(prop.name, prop.raw_value()).unwrap(); - } - - previous_node_level = node.level; - } - } - } - - // End all unclosed nodes - while let Some(node) = node_stack.pop() { - previous_node_level -= 1; - new_fdt.end_node(node).unwrap(); - } - assert_eq!(previous_node_level, 0); - - new_fdt.finish().unwrap() -} diff --git a/kernel/src/vmm/fdt/device.rs b/kernel/src/vmm/fdt/device.rs deleted file mode 100644 index 8b5d9ece..00000000 --- a/kernel/src/vmm/fdt/device.rs +++ /dev/null @@ -1,508 +0,0 @@ -//! Device passthrough and dependency analysis for FDT processing. - -use alloc::{ - collections::{BTreeMap, BTreeSet}, - string::{String, ToString}, - vec::Vec, -}; -use axvm::config::AxVMConfig; -use fdt_parser::{Fdt, Node}; - -/// Return the collection of all passthrough devices in the configuration file and newly added devices found -pub fn find_all_passthrough_devices(vm_cfg: &mut AxVMConfig, fdt: &Fdt) -> Vec { - let initial_device_count = vm_cfg.pass_through_devices().len(); - - // Pre-build node cache, store all nodes by path to improve lookup performance - let node_cache: BTreeMap> = build_optimized_node_cache(fdt); - - // Get the list of configured device names - let initial_device_names: Vec = vm_cfg - .pass_through_devices() - .iter() - .map(|dev| dev.name.clone()) - .collect(); - - // Phase 1: Discover descendant nodes of all passthrough devices in the configuration file - // Build a set of configured devices, using BTreeSet to improve lookup efficiency - let mut configured_device_names: BTreeSet = - initial_device_names.iter().cloned().collect(); - - // Used to store newly discovered related device names - let mut additional_device_names = Vec::new(); - - // Phase 1: Process initial devices and their descendant nodes - // Note: Directly use device paths instead of device names - for device_name in &initial_device_names { - // Get all descendant node paths for this device - let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_name); - trace!( - "Found {} descendant paths for {}", - descendant_paths.len(), - device_name - ); - - for descendant_path in descendant_paths { - if !configured_device_names.contains(&descendant_path) { - trace!("Found descendant device: {descendant_path}"); - configured_device_names.insert(descendant_path.clone()); - - additional_device_names.push(descendant_path.clone()); - } else { - trace!("Device already exists: {descendant_path}"); - } - } - } - - info!( - "Phase 1 completed: Found {} new descendant device names", - additional_device_names.len() - ); - - // Phase 2: Discover dependency nodes for all existing devices (including descendant devices) - let mut dependency_device_names = Vec::new(); - // Use a work queue of device names, including initial devices and descendant device names - let mut devices_to_process: Vec = configured_device_names.iter().cloned().collect(); - let mut processed_devices: BTreeSet = BTreeSet::new(); - - // Build phandle mapping table - let phandle_map = build_phandle_map(fdt); - - // Use work queue to recursively find all dependent devices - while let Some(device_node_path) = devices_to_process.pop() { - // Avoid processing the same device repeatedly - if processed_devices.contains(&device_node_path) { - continue; - } - processed_devices.insert(device_node_path.clone()); - - trace!("Analyzing dependencies for device: {device_node_path}"); - - // Find direct dependencies of the current device - let dependencies = find_device_dependencies(&device_node_path, &phandle_map, &node_cache); - trace!( - "Found {} dependencies: {:?}", - dependencies.len(), - dependencies - ); - for dep_node_name in dependencies { - // Check if dependency is already in configuration - if !configured_device_names.contains(&dep_node_name) { - trace!("Found new dependency device: {dep_node_name}"); - dependency_device_names.push(dep_node_name.clone()); - - // Add dependency device name to work queue to further find its dependencies - devices_to_process.push(dep_node_name.clone()); - configured_device_names.insert(dep_node_name.clone()); - } - } - } - - info!( - "Phase 2 completed: Found {} new dependency device names", - dependency_device_names.len() - ); - - // Phase 3: Find all excluded devices and remove them from the list - // Convert Vec> to Vec - let excluded_device_path: Vec = vm_cfg - .excluded_devices() - .iter() - .flatten() - .cloned() - .collect(); - let mut all_excludes_devices = excluded_device_path.clone(); - let mut process_excludeds: BTreeSet = excluded_device_path.iter().cloned().collect(); - - for device_path in &excluded_device_path { - // Get all descendant node paths for this device - let descendant_paths = get_descendant_nodes_by_path(&node_cache, device_path); - info!( - "Found {} descendant paths for {}", - descendant_paths.len(), - device_path - ); - - for descendant_path in descendant_paths { - if !process_excludeds.contains(&descendant_path) { - trace!("Found descendant device: {descendant_path}"); - process_excludeds.insert(descendant_path.clone()); - - all_excludes_devices.push(descendant_path.clone()); - } else { - trace!("Device already exists: {descendant_path}"); - } - } - } - info!("Found excluded devices: {all_excludes_devices:?}"); - - // Merge all device name lists - let mut all_device_names = initial_device_names.clone(); - all_device_names.extend(additional_device_names); - all_device_names.extend(dependency_device_names); - - // Remove excluded devices from the final list - if !all_excludes_devices.is_empty() { - info!( - "Removing {} excluded devices from the list", - all_excludes_devices.len() - ); - let excluded_set: BTreeSet = all_excludes_devices.into_iter().collect(); - - // Filter out excluded devices - all_device_names.retain(|device_name| { - let should_keep = !excluded_set.contains(device_name); - if !should_keep { - info!("Excluding device: {device_name}"); - } - should_keep - }); - } - - // Phase 4: remove root node from the list - all_device_names.retain(|device_name| device_name != "/"); - - let final_device_count = all_device_names.len(); - info!( - "Passthrough devices analysis completed. Total devices: {} (added: {})", - final_device_count, - final_device_count - initial_device_count - ); - - // Print final device list - for (i, device_name) in all_device_names.iter().enumerate() { - trace!("Final passthrough device[{i}]: {device_name}"); - } - - all_device_names -} - -/// Build the full path of a node based on node level relationships -/// Build the path by traversing all nodes and constructing paths based on level relationships to avoid path conflicts for nodes with the same name -pub fn build_node_path(all_nodes: &[Node], target_index: usize) -> String { - let mut path_stack: Vec = Vec::new(); - - for node in all_nodes.iter().take(target_index + 1) { - let level = node.level; - - if level == 1 { - path_stack.clear(); - if node.name() != "/" { - path_stack.push(node.name().to_string()); - } - } else { - while path_stack.len() >= level - 1 { - path_stack.pop(); - } - path_stack.push(node.name().to_string()); - } - } - - // Build the full path of the current node - if path_stack.is_empty() || (path_stack.len() == 1 && path_stack[0] == "/") { - "/".to_string() - } else { - "/".to_string() + &path_stack.join("/") - } -} - -/// Build a simplified node cache table, traverse all nodes once and group by full path -/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name -pub fn build_optimized_node_cache<'a>(fdt: &'a Fdt) -> BTreeMap>> { - let mut node_cache: BTreeMap>> = BTreeMap::new(); - - let all_nodes: Vec = fdt.all_nodes().collect(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - if let Some(existing_nodes) = node_cache.get(&node_path) - && !existing_nodes.is_empty() - { - error!( - "Duplicate node path found: {} for node '{}' at level {}, existing node: '{}'", - node_path, - node.name(), - node.level, - existing_nodes[0].name() - ); - } - - trace!( - "Adding node to cache: {} (level: {}, index: {})", - node_path, node.level, index - ); - node_cache.entry(node_path).or_default().push(node.clone()); - } - - debug!( - "Built simplified node cache with {} unique device paths", - node_cache.len() - ); - node_cache -} - -/// Build a mapping table from phandle to node information, optimized version using fdt-parser convenience methods -/// Use full path instead of node name -/// Use level relationships to directly build paths, avoiding path conflicts for nodes with the same name -fn build_phandle_map(fdt: &Fdt) -> BTreeMap)> { - let mut phandle_map = BTreeMap::new(); - - let all_nodes: Vec = fdt.all_nodes().collect(); - - for (index, node) in all_nodes.iter().enumerate() { - let node_path = build_node_path(&all_nodes, index); - - // Collect node properties - let mut phandle = None; - let mut cells_map = BTreeMap::new(); - for prop in node.propertys() { - match prop.name { - "phandle" | "linux,phandle" => { - phandle = Some(prop.u32()); - } - "#address-cells" - | "#size-cells" - | "#clock-cells" - | "#reset-cells" - | "#gpio-cells" - | "#interrupt-cells" - | "#power-domain-cells" - | "#thermal-sensor-cells" - | "#phy-cells" - | "#dma-cells" - | "#sound-dai-cells" - | "#mbox-cells" - | "#pwm-cells" - | "#iommu-cells" => { - cells_map.insert(prop.name.to_string(), prop.u32()); - } - _ => {} - } - } - - // If phandle is found, store it together with the node's full path - if let Some(ph) = phandle { - phandle_map.insert(ph, (node_path, cells_map)); - } - } - phandle_map -} - -/// Parse properties containing phandle references intelligently based on #*-cells properties -/// Supports multiple formats: -/// - Single phandle: -/// - phandle+specifier: -/// - Multiple phandle references: -fn parse_phandle_property_with_cells( - prop_data: &[u8], - prop_name: &str, - phandle_map: &BTreeMap)>, -) -> Vec<(u32, Vec)> { - let mut results = Vec::new(); - - debug!( - "Parsing property '{}' with cells info, data length: {} bytes", - prop_name, - prop_data.len() - ); - - if prop_data.is_empty() || prop_data.len() % 4 != 0 { - warn!( - "Property '{}' data length ({} bytes) is invalid", - prop_name, - prop_data.len() - ); - return results; - } - - let u32_values: Vec = prop_data - .chunks(4) - .map(|chunk| u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]])) - .collect(); - - let mut i = 0; - while i < u32_values.len() { - let potential_phandle = u32_values[i]; - - // Check if it's a valid phandle - if let Some((device_name, cells_info)) = phandle_map.get(&potential_phandle) { - // Determine the number of cells required based on property name - let cells_count = get_cells_count_for_property(prop_name, cells_info); - trace!( - "Property '{prop_name}' requires {cells_count} cells for device '{device_name}'" - ); - - // Check if there's enough data - if i + cells_count < u32_values.len() { - let specifiers: Vec = u32_values[i + 1..=i + cells_count].to_vec(); - debug!( - "Parsed phandle reference: phandle={potential_phandle:#x}, specifiers={specifiers:?}" - ); - results.push((potential_phandle, specifiers)); - i += cells_count + 1; // Skip phandle and all specifiers - } else { - warn!( - "Property:{} not enough data for phandle {:#x}, expected {} cells but only {} values remaining", - prop_name, - potential_phandle, - cells_count, - u32_values.len() - i - 1 - ); - break; - } - } else { - // If not a valid phandle, skip this value - i += 1; - } - } - - results -} - -/// Determine the required number of cells based on property name and target node's cells information -fn get_cells_count_for_property(prop_name: &str, cells_info: &BTreeMap) -> usize { - let cells_property = match prop_name { - "clocks" | "assigned-clocks" => "#clock-cells", - "resets" => "#reset-cells", - "power-domains" => "#power-domain-cells", - "phys" => "#phy-cells", - "interrupts" | "interrupts-extended" => "#interrupt-cells", - "gpios" => "#gpio-cells", - _ if prop_name.ends_with("-gpios") || prop_name.ends_with("-gpio") => "#gpio-cells", - "dmas" => "#dma-cells", - "thermal-sensors" => "#thermal-sensor-cells", - "sound-dai" => "#sound-dai-cells", - "mboxes" => "#mbox-cells", - "pwms" => "#pwm-cells", - _ => { - debug!("Unknown property '{prop_name}', defaulting to 0 cell"); - return 0; - } - }; - - cells_info.get(cells_property).copied().unwrap_or(0) as usize -} - -/// Generic phandle property parsing function -/// Parse phandle references according to cells information with correct block size -/// Support single phandle and multiple phandle+specifier formats -/// Return full path instead of node name -fn parse_phandle_property( - prop_data: &[u8], - prop_name: &str, - phandle_map: &BTreeMap)>, -) -> Vec { - let mut dependencies = Vec::new(); - - let phandle_refs = parse_phandle_property_with_cells(prop_data, prop_name, phandle_map); - - for (phandle, specifiers) in phandle_refs { - if let Some((device_path, _cells_info)) = phandle_map.get(&phandle) { - let spec_info = if !specifiers.is_empty() { - format!(" (specifiers: {specifiers:?})") - } else { - String::new() - }; - debug!( - "Found {prop_name} dependency: phandle={phandle:#x}, device={device_path}{spec_info}" - ); - dependencies.push(device_path.clone()); - } - } - - dependencies -} - -/// Device property classifier - used to identify properties that require special handling -struct DevicePropertyClassifier; - -impl DevicePropertyClassifier { - /// Phandle properties that require special handling - includes all properties that need dependency resolution - const PHANDLE_PROPERTIES: &'static [&'static str] = &[ - "clocks", - "power-domains", - "phys", - "resets", - "dmas", - "thermal-sensors", - "mboxes", - "assigned-clocks", - "interrupt-parent", - "phy-handle", - "msi-parent", - "memory-region", - "syscon", - "regmap", - "iommus", - "interconnects", - "nvmem-cells", - "sound-dai", - "pinctrl-0", - "pinctrl-1", - "pinctrl-2", - "pinctrl-3", - "pinctrl-4", - ]; - - /// Determine if it's a phandle property that requires handling - fn is_phandle_property(prop_name: &str) -> bool { - Self::PHANDLE_PROPERTIES.contains(&prop_name) - || prop_name.ends_with("-supply") - || prop_name == "gpios" - || prop_name.ends_with("-gpios") - || prop_name.ends_with("-gpio") - || (prop_name.contains("cells") && !prop_name.starts_with("#") && prop_name.len() >= 4) - } -} - -/// Find device dependencies -fn find_device_dependencies( - device_node_path: &str, - phandle_map: &BTreeMap)>, - node_cache: &BTreeMap>, // Add node_cache parameter -) -> Vec { - let mut dependencies = Vec::new(); - - // Directly find nodes from node_cache, avoiding traversing all nodes - if let Some(nodes) = node_cache.get(device_node_path) { - // Traverse all properties of nodes to find dependencies - for node in nodes { - for prop in node.propertys() { - // Determine if it's a phandle property that needs to be processed - if DevicePropertyClassifier::is_phandle_property(prop.name) { - let mut prop_deps = - parse_phandle_property(prop.raw_value(), prop.name, phandle_map); - dependencies.append(&mut prop_deps); - } - } - } - } - - dependencies -} - -/// Get all descendant nodes based on parent node path (including child nodes, grandchild nodes, etc.) -/// Find all descendant nodes by looking up nodes with parent node path as prefix in node_cache -fn get_descendant_nodes_by_path<'a>( - node_cache: &'a BTreeMap>>, - parent_path: &str, -) -> Vec { - let mut descendant_paths = Vec::new(); - - // Special handling if parent path is root path - let search_prefix = if parent_path == "/" { - "/".to_string() - } else { - parent_path.to_string() + "/" - }; - - // Traverse node_cache, find all nodes with parent path as prefix - for path in node_cache.keys() { - // Check if path has parent path as prefix (and is not the parent path itself) - if path.starts_with(&search_prefix) && path.len() > search_prefix.len() { - // This is a descendant node path, add to results - descendant_paths.push(path.clone()); - } - } - - descendant_paths -} diff --git a/kernel/src/vmm/fdt/mod.rs b/kernel/src/vmm/fdt/mod.rs deleted file mode 100644 index b76da293..00000000 --- a/kernel/src/vmm/fdt/mod.rs +++ /dev/null @@ -1,122 +0,0 @@ -//! FDT (Flattened Device Tree) processing module for AxVisor. -//! -//! This module provides functionality for parsing and processing device tree blobs, -//! including CPU configuration, passthrough device detection, and FDT generation. - -mod create; -mod device; -mod parser; -mod print; - -use alloc::collections::BTreeMap; -use alloc::vec::Vec; -use axvm::config::{AxVMConfig, AxVMCrateConfig}; -use fdt_parser::Fdt; -use lazyinit::LazyInit; -use spin::Mutex; - -pub use parser::*; -// pub use print::print_fdt; -pub use create::*; -pub use device::build_node_path; - -use crate::vmm::config::{config, get_vm_dtb_arc}; - -// DTB cache for generated device trees -static GENERATED_DTB_CACHE: LazyInit>>> = LazyInit::new(); - -/// Initialize the DTB cache -pub fn init_dtb_cache() { - GENERATED_DTB_CACHE.init_once(Mutex::new(BTreeMap::new())); -} - -/// Get reference to the DTB cache -pub fn dtb_cache() -> &'static Mutex>> { - GENERATED_DTB_CACHE.get().unwrap() -} - -/// Generate guest FDT cache the result -/// # Return Value -/// Returns the generated DTB data and stores it in the global cache -pub fn crate_guest_fdt_with_cache(dtb_data: Vec, crate_config: &AxVMCrateConfig) { - // Store data in global cache - let mut cache_lock = dtb_cache().lock(); - cache_lock.insert(crate_config.base.id, dtb_data); -} - -/// Handle all FDT-related operations for aarch64 architecture -pub fn handle_fdt_operations(vm_config: &mut AxVMConfig, vm_create_config: &AxVMCrateConfig) { - let host_fdt_bytes = get_host_fdt(); - let host_fdt = Fdt::from_bytes(host_fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - set_phys_cpu_sets(vm_config, &host_fdt, vm_create_config); - - if let Some(provided_dtb) = get_developer_provided_dtb(vm_config, vm_create_config) { - info!("VM[{}] found DTB , parsing...", vm_config.id()); - update_provided_fdt(&provided_dtb, host_fdt_bytes, vm_create_config); - } else { - info!( - "VM[{}] DTB not found, generating based on the configuration file.", - vm_config.id() - ); - setup_guest_fdt_from_vmm(host_fdt_bytes, vm_config, vm_create_config); - } - - // Overlay VM config with the given DTB. - if let Some(dtb_arc) = get_vm_dtb_arc(vm_config) { - let dtb = dtb_arc.as_ref(); - parse_passthrough_devices_address(vm_config, dtb); - parse_vm_interrupt(vm_config, dtb); - } else { - error!( - "VM[{}] DTB not found in memory, skipping...", - vm_config.id() - ); - } -} - -pub fn get_developer_provided_dtb( - vm_cfg: &AxVMConfig, - crate_config: &AxVMCrateConfig, -) -> Option> { - match crate_config.kernel.image_location.as_deref() { - Some("memory") => { - let vm_imags = config::get_memory_images() - .iter() - .find(|&v| v.id == vm_cfg.id())?; - - if let Some(dtb) = vm_imags.dtb { - info!("DTB file in memory, size: 0x{:x}", dtb.len()); - return Some(dtb.to_vec()); - } - } - #[cfg(feature = "fs")] - Some("fs") => { - use axerrno::ax_err_type; - use std::io::{BufReader, Read}; - if let Some(dtb_path) = &crate_config.kernel.dtb_path { - let (dtb_file, dtb_size) = - crate::vmm::images::fs::open_image_file(dtb_path).unwrap(); - info!("DTB file in fs, size: 0x{:x}", dtb_size); - - let mut file = BufReader::new(dtb_file); - let mut dtb_buffer = vec![0; dtb_size]; - - file.read_exact(&mut dtb_buffer) - .map_err(|err| { - ax_err_type!( - Io, - format!("Failed in reading from file {}, err {:?}", dtb_path, err) - ) - }) - .unwrap(); - return Some(dtb_buffer); - } - } - _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n." - ), - } - None -} diff --git a/kernel/src/vmm/fdt/parser.rs b/kernel/src/vmm/fdt/parser.rs deleted file mode 100644 index 5eb81388..00000000 --- a/kernel/src/vmm/fdt/parser.rs +++ /dev/null @@ -1,397 +0,0 @@ -//! FDT parsing and processing functionality. - -use alloc::{string::ToString, vec::Vec}; -use axvm::config::{AxVMConfig, AxVMCrateConfig, PassThroughDeviceConfig}; -use fdt_parser::{Fdt, FdtHeader, PciRange, PciSpace}; - -use crate::vmm::fdt::crate_guest_fdt_with_cache; -use crate::vmm::fdt::create::update_cpu_node; - -pub fn get_host_fdt() -> &'static [u8] { - const FDT_VALID_MAGIC: u32 = 0xd00d_feed; - let bootarg: usize = std::os::arceos::modules::axhal::dtb::get_bootarg(); - let header = unsafe { - core::slice::from_raw_parts(bootarg as *const u8, core::mem::size_of::()) - }; - let fdt_header = FdtHeader::from_bytes(header) - .map_err(|e| format!("Failed to parse FDT header: {e:#?}")) - .unwrap(); - - if fdt_header.magic.get() != FDT_VALID_MAGIC { - error!( - "FDT magic is invalid, expected {:#x}, got {:#x}", - FDT_VALID_MAGIC, - fdt_header.magic.get() - ); - } - - unsafe { core::slice::from_raw_parts(bootarg as *const u8, fdt_header.total_size()) } -} - -pub fn setup_guest_fdt_from_vmm( - fdt_bytes: &[u8], - vm_cfg: &mut AxVMConfig, - crate_config: &AxVMCrateConfig, -) { - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - - // Call the modified function and get the returned device name list - let passthrough_device_names = super::device::find_all_passthrough_devices(vm_cfg, &fdt); - - let dtb_data = super::create::crate_guest_fdt(&fdt, &passthrough_device_names, crate_config); - crate_guest_fdt_with_cache(dtb_data, crate_config); -} - -pub fn set_phys_cpu_sets(vm_cfg: &mut AxVMConfig, fdt: &Fdt, crate_config: &AxVMCrateConfig) { - // Find and parse CPU information from host DTB - let host_cpus: Vec<_> = fdt.find_nodes("/cpus/cpu").collect(); - info!("Found {} host CPU nodes", &host_cpus.len()); - - let phys_cpu_ids = crate_config - .base - .phys_cpu_ids - .as_ref() - .expect("ERROR: phys_cpu_ids not found in config.toml"); - - // Collect all CPU node information into Vec to avoid using iterators multiple times - let cpu_nodes_info: Vec<_> = host_cpus - .iter() - .filter_map(|cpu_node| { - if let Some(mut cpu_reg) = cpu_node.reg() { - if let Some(r) = cpu_reg.next() { - info!( - "CPU node: {}, phys_cpu_id: 0x{:x}", - cpu_node.name(), - r.address - ); - Some((cpu_node.name().to_string(), r.address as usize)) - } else { - None - } - } else { - None - } - }) - .collect(); - // Create mapping from phys_cpu_id to physical CPU index - // Collect all unique CPU addresses, maintaining the order of appearance in the device tree - let mut unique_cpu_addresses = Vec::new(); - for (_, cpu_address) in &cpu_nodes_info { - if !unique_cpu_addresses.contains(cpu_address) { - unique_cpu_addresses.push(*cpu_address); - } else { - panic!("Duplicate CPU address found"); - } - } - - // Assign index to each CPU address in the device tree and print detailed information - for (index, &cpu_address) in unique_cpu_addresses.iter().enumerate() { - // Find all CPU nodes using this address - for (cpu_name, node_address) in &cpu_nodes_info { - if *node_address == cpu_address { - debug!( - " CPU node: {cpu_name}, address: 0x{cpu_address:x}, assigned index: {index}" - ); - break; // Print each address only once - } - } - } - - // Calculate phys_cpu_sets based on phys_cpu_ids in vcpu_mappings - let mut new_phys_cpu_sets = Vec::new(); - for phys_cpu_id in phys_cpu_ids { - // Find the index corresponding to phys_cpu_id in unique_cpu_addresses - if let Some(cpu_index) = unique_cpu_addresses - .iter() - .position(|&addr| addr == *phys_cpu_id) - { - let cpu_mask = 1usize << cpu_index; // Convert index to mask bit - new_phys_cpu_sets.push(cpu_mask); - debug!( - "vCPU {} with phys_cpu_id 0x{:x} mapped to CPU index {} (mask: 0x{:x})", - vm_cfg.id(), - phys_cpu_id, - cpu_index, - cpu_mask - ); - } else { - error!( - "vCPU {} with phys_cpu_id 0x{:x} not found in device tree!", - vm_cfg.id(), - phys_cpu_id - ); - } - } - - // Update phys_cpu_sets in VM configuration (if VM configuration supports setting) - info!("Calculated phys_cpu_sets: {new_phys_cpu_sets:?}"); - - vm_cfg - .phys_cpu_ls_mut() - .set_guest_cpu_sets(new_phys_cpu_sets); - - debug!( - "vcpu_mappings: {:?}", - vm_cfg.phys_cpu_ls_mut().get_vcpu_affinities_pcpu_ids() - ); -} - -/// Add address mapping configuration for a device -fn add_device_address_config( - vm_cfg: &mut AxVMConfig, - node_name: &str, - base_address: usize, - size: usize, - index: usize, - prefix: Option<&str>, -) { - // Only process devices with address information - if size == 0 { - return; - } - - // Create a device configuration for each address segment - let device_name = if index == 0 { - match prefix { - Some(p) => format!("{node_name}-{p}"), - None => node_name.to_string(), - } - } else { - match prefix { - Some(p) => format!("{node_name}-{p}-region{index}"), - None => format!("{node_name}-region{index}"), - } - }; - - // Add new device configuration - let pt_dev = axvm::config::PassThroughDeviceConfig { - name: device_name, - base_gpa: base_address, - base_hpa: base_address, - length: size, - irq_id: 0, - }; - vm_cfg.add_pass_through_device(pt_dev); -} - -/// Add ranges property configuration for PCIe devices -fn add_pci_ranges_config(vm_cfg: &mut AxVMConfig, node_name: &str, range: &PciRange, index: usize) { - let base_address = range.cpu_address as usize; - let size = range.size as usize; - - // Only process devices with address information - if size == 0 { - return; - } - - // Create a device configuration for each address segment - let prefix = match range.space { - PciSpace::Configuration => "config", - PciSpace::IO => "io", - PciSpace::Memory32 => "mem32", - PciSpace::Memory64 => "mem64", - }; - - let device_name = if index == 0 { - format!("{node_name}-{prefix}") - } else { - format!("{node_name}-{prefix}-region{index}") - }; - - // Add new device configuration - let pt_dev = axvm::config::PassThroughDeviceConfig { - name: device_name, - base_gpa: base_address, - base_hpa: base_address, - length: size, - irq_id: 0, - }; - vm_cfg.add_pass_through_device(pt_dev); - - trace!( - "Added PCIe passthrough device {}: base=0x{:x}, size=0x{:x}, space={:?}", - node_name, base_address, size, range.space - ); -} - -pub fn parse_passthrough_devices_address(vm_cfg: &mut AxVMConfig, dtb: &[u8]) { - let devices = vm_cfg.pass_through_devices().to_vec(); - if !devices.is_empty() && devices[0].length != 0 { - for (index, device) in devices.iter().enumerate() { - add_device_address_config( - vm_cfg, - &device.name, - device.base_gpa, - device.length, - index, - None, - ); - } - } else { - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - - // Clear existing passthrough device configurations - vm_cfg.clear_pass_through_devices(); - - // Traverse all device tree nodes - for node in fdt.all_nodes() { - // Skip root node - if node.name() == "/" || node.name().starts_with("memory") { - continue; - } - - let node_name = node.name().to_string(); - - // Check if it's a PCIe device node - if node_name.starts_with("pcie@") || node_name.contains("pci") { - // Process PCIe device's ranges property - if let Some(pci) = node.clone().into_pci() - && let Ok(ranges) = pci.ranges() - { - for (index, range) in ranges.enumerate() { - add_pci_ranges_config(vm_cfg, &node_name, &range, index); - } - } - - // Process PCIe device's reg property (ECAM space) - if let Some(reg_iter) = node.reg() { - for (index, reg) in reg_iter.enumerate() { - let base_address = reg.address as usize; - let size = reg.size.unwrap_or(0); - - add_device_address_config( - vm_cfg, - &node_name, - base_address, - size, - index, - Some("ecam"), - ); - } - } - } else { - // Get device's reg property (process regular devices) - if let Some(reg_iter) = node.reg() { - // Process all address segments of the device - for (index, reg) in reg_iter.enumerate() { - // Get device's address and size information - let base_address = reg.address as usize; - let size = reg.size.unwrap_or(0); - - add_device_address_config( - vm_cfg, - &node_name, - base_address, - size, - index, - None, - ); - } - } - } - } - trace!( - "All passthrough devices: {:#x?}", - vm_cfg.pass_through_devices() - ); - debug!( - "Finished parsing passthrough devices, total: {}", - vm_cfg.pass_through_devices().len() - ); - } -} - -pub fn parse_vm_interrupt(vm_cfg: &mut AxVMConfig, dtb: &[u8]) { - const GIC_PHANDLE: usize = 1; - let fdt = Fdt::from_bytes(dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - - for node in fdt.all_nodes() { - let name = node.name(); - - if name.starts_with("memory") { - continue; - } - // Skip the interrupt controller, as we will use vGIC - // TODO: filter with compatible property and parse its phandle from DT; maybe needs a second pass? - else if name.starts_with("interrupt-controller") - || name.starts_with("intc") - || name.starts_with("its") - { - info!("skipping node {name} to use vGIC"); - continue; - } - - // Collect all GIC_SPI interrupts and add them to vGIC - if let Some(interrupts) = node.interrupts() { - // TODO: skip non-GIC interrupt - if let Some(parent) = node.interrupt_parent() { - trace!("node: {}, intr parent: {}", name, parent.node.name()); - if let Some(phandle) = parent.node.phandle() { - if phandle.as_usize() != GIC_PHANDLE { - debug!( - "node: {}, intr parent: {}, phandle: 0x{:x} is not GIC!", - name, - parent.node.name(), - phandle.as_usize() - ); - } - } else { - warn!( - "node: {}, intr parent: {} no phandle!", - name, - parent.node.name(), - ); - } - } else { - warn!("node: {name} no interrupt parent!"); - } - - for interrupt in interrupts { - // - for (k, v) in interrupt.enumerate() { - match k { - 0 => { - if v == 0 { - trace!("node: {name}, GIC_SPI"); - } else { - debug!("node: {name}, intr type: {v}, not GIC_SPI, not supported!"); - break; - } - } - 1 => { - trace!("node: {name}, interrupt id: 0x{v:x}"); - vm_cfg.add_pass_through_spi(v); - } - 2 => { - trace!("node: {name}, interrupt mode: 0x{v:x}"); - } - _ => { - warn!("unknown interrupt property {k}:0x{v:x}") - } - } - } - } - } - } - - vm_cfg.add_pass_through_device(PassThroughDeviceConfig { - name: "Fake Node".to_string(), - base_gpa: 0x0, - base_hpa: 0x0, - length: 0x20_0000, - irq_id: 0, - }); -} - -pub fn update_provided_fdt(provided_dtb: &[u8], host_dtb: &[u8], crate_config: &AxVMCrateConfig) { - let provided_fdt = Fdt::from_bytes(provided_dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - let host_fdt = Fdt::from_bytes(host_dtb) - .expect("Failed to parse DTB image, perhaps the DTB is invalid or corrupted"); - let provided_dtb_data = update_cpu_node(&provided_fdt, &host_fdt, crate_config); - crate_guest_fdt_with_cache(provided_dtb_data, crate_config); -} diff --git a/kernel/src/vmm/fdt/print.rs b/kernel/src/vmm/fdt/print.rs deleted file mode 100644 index 29f63f35..00000000 --- a/kernel/src/vmm/fdt/print.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! FDT parsing and processing functionality. - -use fdt_parser::{Fdt, FdtHeader}; - -#[allow(dead_code)] -pub fn print_fdt(fdt_addr: usize) { - const FDT_VALID_MAGIC: u32 = 0xd00d_feed; - let header = unsafe { - core::slice::from_raw_parts(fdt_addr as *const u8, core::mem::size_of::()) - }; - let fdt_header = FdtHeader::from_bytes(header) - .map_err(|e| format!("Failed to parse FDT header: {e:#?}")) - .unwrap(); - - if fdt_header.magic.get() != FDT_VALID_MAGIC { - error!( - "FDT magic is invalid, expected {:#x}, got {:#x}", - FDT_VALID_MAGIC, - fdt_header.magic.get() - ); - return; - } - - let fdt_bytes = - unsafe { core::slice::from_raw_parts(fdt_addr as *const u8, fdt_header.total_size()) }; - - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - - // Statistics of node count and level distribution - let mut node_count = 0; - let mut level_counts = alloc::collections::BTreeMap::new(); - let mut max_level = 0; - - info!("=== FDT Node Information Statistics ==="); - - // Traverse all nodes once for statistics (following optimization strategy) - for node in fdt.all_nodes() { - node_count += 1; - - // Count nodes by level - *level_counts.entry(node.level).or_insert(0) += 1; - - // Record maximum level - if node.level > max_level { - max_level = node.level; - } - - // Count property numbers - let node_properties_count = node.propertys().count(); - - trace!( - "Node[{}]: {} (Level: {}, Properties: {})", - node_count, - node.name(), - node.level, - node_properties_count - ); - - for prop in node.propertys() { - trace!( - "Properties: {}, Raw_value: {:x?}", - prop.name, - prop.raw_value() - ); - } - } - - info!("=== FDT Statistics Results ==="); - info!("Total node count: {node_count}"); - info!("FDT total size: {} bytes", fdt_header.total_size()); - info!("Maximum level depth: {max_level}"); - - info!("Node distribution by level:"); - for (level, count) in level_counts { - let percentage = (count as f32 / node_count as f32) * 100.0; - info!(" Level {level}: {count} nodes ({percentage:.1}%)"); - } -} - -#[allow(dead_code)] -pub fn print_guest_fdt(fdt_bytes: &[u8]) { - let fdt = Fdt::from_bytes(fdt_bytes) - .map_err(|e| format!("Failed to parse FDT: {e:#?}")) - .expect("Failed to parse FDT"); - // Statistics of node count and level distribution - let mut node_count = 0; - let mut level_counts = alloc::collections::BTreeMap::new(); - let mut max_level = 0; - - info!("=== FDT Node Information Statistics ==="); - - // Traverse all nodes once for statistics (following optimization strategy) - for node in fdt.all_nodes() { - node_count += 1; - - // Count nodes by level - *level_counts.entry(node.level).or_insert(0) += 1; - - // Record maximum level - if node.level > max_level { - max_level = node.level; - } - - // Count property numbers - let node_properties_count = node.propertys().count(); - - info!( - "Node[{}]: {} (Level: {}, Properties: {})", - node_count, - node.name(), - node.level, - node_properties_count - ); - - for prop in node.propertys() { - info!( - "Properties: {}, Raw_value: {:x?}", - prop.name, - prop.raw_value() - ); - } - } - - info!("=== FDT Statistics Results ==="); - info!("Total node count: {node_count}"); - info!("Maximum level depth: {max_level}"); - - info!("Node distribution by level:"); - for (level, count) in level_counts { - let percentage = (count as f32 / node_count as f32) * 100.0; - info!(" Level {level}: {count} nodes ({percentage:.1}%)"); - } -} diff --git a/kernel/src/vmm/images/mod.rs b/kernel/src/vmm/images/mod.rs index 98e10547..62b5f494 100644 --- a/kernel/src/vmm/images/mod.rs +++ b/kernel/src/vmm/images/mod.rs @@ -4,7 +4,7 @@ use axvm::GuestPhysAddr; use axvm::config::{AxVMCrateConfig, VMImageConfig, VMImagesConfig}; use axvmconfig::ImageLocation; -use crate::vmm::config::config::MemoryImage; +use crate::config::config::MemoryImage; mod linux; @@ -71,8 +71,73 @@ fn memory_image(config: &AxVMCrateConfig) -> &'static MemoryImage { #[cfg(feature = "fs")] pub mod fs { use super::*; + use alloc::vec::Vec; - pub fn load_images_fs(_config: &AxVMCrateConfig) -> anyhow::Result { - todo!() + pub fn load_images_fs(config: &AxVMCrateConfig) -> anyhow::Result { + // Load kernel image + let kernel = load_image_file(&config.kernel.kernel_path)?; + let kernel = VMImageConfig { + gpa: config.kernel.kernel_load_addr.map(GuestPhysAddr::from), + data: kernel, + }; + + // Load BIOS image if configured + let bios = if let Some(bios_path) = &config.kernel.bios_path { + let bios_data = load_image_file(bios_path)?; + Some(VMImageConfig { + gpa: config.kernel.bios_load_addr.map(GuestPhysAddr::from), + data: bios_data, + }) + } else { + None + }; + + // Load DTB image if configured + let dtb = if let Some(dtb_path) = &config.kernel.dtb_path { + let dtb_data = load_image_file(dtb_path)?; + Some(VMImageConfig { + gpa: config.kernel.dtb_load_addr.map(GuestPhysAddr::from), + data: dtb_data, + }) + } else { + None + }; + + // Load ramdisk image if configured + let ramdisk = if let Some(ramdisk_path) = &config.kernel.ramdisk_path { + let ramdisk_data = load_image_file(ramdisk_path)?; + Some(VMImageConfig { + gpa: config.kernel.ramdisk_load_addr.map(GuestPhysAddr::from), + data: ramdisk_data, + }) + } else { + None + }; + + Ok(VMImagesConfig { + kernel, + bios, + dtb, + ramdisk, + }) + } + + fn load_image_file(path: &str) -> anyhow::Result> { + use axstd::io::Read; + + let mut file = axstd::fs::File::open(path) + .map_err(|e| anyhow::anyhow!("Failed to open image file '{}': {}", path, e))?; + + let metadata = file + .metadata() + .map_err(|e| anyhow::anyhow!("Failed to get metadata for '{}': {}", path, e))?; + + let file_size = metadata.len() as usize; + let mut buffer = Vec::with_capacity(file_size); + + file.read_to_end(&mut buffer) + .map_err(|e| anyhow::anyhow!("Failed to read image file '{}': {}", path, e))?; + + Ok(buffer) } } diff --git a/kernel/src/vmm/mod.rs b/kernel/src/vmm/mod.rs index 95801907..1387ec36 100644 --- a/kernel/src/vmm/mod.rs +++ b/kernel/src/vmm/mod.rs @@ -6,7 +6,7 @@ pub mod images; // pub mod timer; pub mod vm_list; -use axvm::AxVMConfig; +use axvm::{AxVMConfig, VmId}; /// Initialize the VMM. /// @@ -25,12 +25,12 @@ pub fn start_preconfigured_vms() -> anyhow::Result<()> { Ok(()) } -pub fn start_vm(config: AxVMConfig) -> anyhow::Result<()> { +pub fn start_vm(config: AxVMConfig) -> anyhow::Result { debug!("Starting guest VM `{}`", config.name()); let vm = axvm::Vm::new(config)?; let vm = vm_list::push_vm(vm); vm.boot()?; - Ok(()) + Ok(vm.id()) } pub fn wait_for_all_vms_exit() { diff --git a/kernel/src/vmm/vcpus.rs b/kernel/src/vmm/vcpus.rs index 22cb47fe..666d029a 100644 --- a/kernel/src/vmm/vcpus.rs +++ b/kernel/src/vmm/vcpus.rs @@ -240,7 +240,7 @@ pub(crate) fn notify_all_vcpus(vm_id: usize) { /// /// This should be called after all VCpu threads have exited to avoid resource leaks. /// It will join all VCpu tasks to ensure they are fully cleaned up. -pub(crate) fn cleanup_vm_vcpus(vm_id: usize) { +pub fn cleanup_vm_vcpus(vm_id: usize) { if let Some(vm_vcpus) = VM_VCPU_TASK_WAIT_QUEUE.remove(&vm_id) { let task_count = vm_vcpus.vcpu_task_list.len(); diff --git a/kernel/src/vmm/vm_list.rs b/kernel/src/vmm/vm_list.rs index 2a878a04..33f39ff1 100644 --- a/kernel/src/vmm/vm_list.rs +++ b/kernel/src/vmm/vm_list.rs @@ -1,5 +1,4 @@ use alloc::{collections::BTreeMap, sync::Arc, vec::Vec}; - use spin::Mutex; pub type VMRef = Arc; diff --git a/modules/axfs/src/fs/fatfs.rs b/modules/axfs/src/fs/fatfs.rs index da0b6f11..b62d25cb 100644 --- a/modules/axfs/src/fs/fatfs.rs +++ b/modules/axfs/src/fs/fatfs.rs @@ -4,8 +4,8 @@ use core::cell::OnceCell; use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; -use spin::Mutex; use fatfs::{Dir, File, LossyOemCpConverter, NullTimeProvider, Read, Seek, SeekFrom, Write}; +use spin::Mutex; use crate::dev::{Disk, Partition}; diff --git a/modules/axvm b/modules/axvm index bce19d92..b4b88e06 160000 --- a/modules/axvm +++ b/modules/axvm @@ -1 +1 @@ -Subproject commit bce19d92ee18ee6e38d480acbd6c90bc958a1681 +Subproject commit b4b88e06703b20b2f3b3eef409fafbf191cea679