diff --git a/src/main.rs b/src/main.rs index f9eeca0..68000cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,10 @@ struct VMMOpts { /// Stdout console file path #[clap(long)] console: Option, + + /// no-console + #[clap(long)] + no_console: bool, } #[derive(Debug)] @@ -47,11 +51,17 @@ fn main() -> Result<(), Error> { // * Memory size (in MB) // * Path to a Linux kernel // * Optional path to console file - vmm.configure(opts.cpus, opts.memory, &opts.kernel, opts.console) - .map_err(Error::VmmConfigure)?; + vmm.configure( + opts.cpus, + opts.memory, + &opts.kernel, + opts.console, + opts.no_console, + ) + .map_err(Error::VmmConfigure)?; // Run the VMM - vmm.run().map_err(Error::VmmRun)?; + vmm.run(opts.no_console).map_err(Error::VmmRun)?; Ok(()) } diff --git a/src/vmm/src/cpu/mod.rs b/src/vmm/src/cpu/mod.rs index d81fdcf..dcadd67 100644 --- a/src/vmm/src/cpu/mod.rs +++ b/src/vmm/src/cpu/mod.rs @@ -215,20 +215,30 @@ impl Vcpu { } /// vCPU emulation loop. - pub fn run(&mut self) { + pub fn run(&mut self, no_console: bool, should_stop: Arc>) { // Call into KVM to launch (VMLAUNCH) or resume (VMRESUME) the virtual CPU. // This is a blocking function, it only returns for either an error or a // VM-Exit. In the latter case, we can inspect the exit reason. - match self.vcpu_fd.run() { + println!("Before running vCPU {}...", self.index); + let run = self.vcpu_fd.run(); + println!("After running vCPU {}...", self.index); + + match run { Ok(exit_reason) => match exit_reason { // The VM stopped (Shutdown ot HLT). VcpuExit::Shutdown | VcpuExit::Hlt => { println!("Guest shutdown: {:?}. Bye!", exit_reason); - let stdin = io::stdin(); - let stdin_lock = stdin.lock(); - stdin_lock.set_canon_mode().unwrap(); - unsafe { libc::exit(0) }; + if no_console { + println!("Exiting... {:?}", self.index); + *should_stop.lock().unwrap() = true; + } else { + let stdin = io::stdin(); + let stdin_lock = stdin.lock(); + stdin_lock.set_canon_mode().unwrap(); + + unsafe { libc::exit(0) }; + } } // This is a PIO write, i.e. the guest is trying to write diff --git a/src/vmm/src/kernel.rs b/src/vmm/src/kernel.rs index bf094d1..5c51167 100644 --- a/src/vmm/src/kernel.rs +++ b/src/vmm/src/kernel.rs @@ -45,6 +45,7 @@ const HIMEM_START: u64 = 0x0010_0000; // 1 MB const CMDLINE_START: u64 = 0x0002_0000; // Default command line const CMDLINE: &str = "console=ttyS0 i8042.nokbd reboot=k panic=1 pci=off"; +const CMDLINE_NO_CONSOLE: &str = "i8042.nokbd reboot=k panic=1 pci=off"; // remove console=ttyS0 fn add_e820_entry( params: &mut boot_params, @@ -109,6 +110,7 @@ pub fn build_bootparams( pub fn kernel_setup( guest_memory: &GuestMemoryMmap, kernel_path: PathBuf, + no_console: bool, ) -> Result { let mut kernel_image = File::open(kernel_path).map_err(Error::IO)?; let zero_page_addr = GuestAddress(ZEROPG_START); @@ -125,14 +127,19 @@ pub fn kernel_setup( // Generate boot parameters. let mut bootparams = build_bootparams(guest_memory, GuestAddress(HIMEM_START))?; + let right_cmdline = if no_console { + CMDLINE_NO_CONSOLE + } else { + CMDLINE + }; + // Add the kernel command line to the boot parameters. bootparams.hdr.cmd_line_ptr = CMDLINE_START as u32; - bootparams.hdr.cmdline_size = CMDLINE.len() as u32 + 1; + bootparams.hdr.cmdline_size = right_cmdline.len() as u32 + 1; // Load the kernel command line into guest memory. - let mut cmdline = Cmdline::new(CMDLINE.len() + 1).map_err(Error::Cmdline)?; - - cmdline.insert_str(CMDLINE).map_err(Error::Cmdline)?; + let mut cmdline = Cmdline::new(right_cmdline.len() + 1).map_err(Error::Cmdline)?; + cmdline.insert_str(right_cmdline).map_err(Error::Cmdline)?; load_cmdline( guest_memory, GuestAddress(CMDLINE_START), diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index 160dd2f..50e3a41 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -8,6 +8,7 @@ extern crate linux_loader; extern crate vm_memory; extern crate vm_superio; +use std::any::Any; use std::io::stdout; use std::os::unix::io::AsRawFd; use std::os::unix::prelude::RawFd; @@ -66,6 +67,10 @@ pub enum Error { TerminalConfigure(kvm_ioctls::Error), /// Console configuration error ConsoleError(io::Error), + /// Access thread handler error + AccessThreadHandlerError, + /// Join thread error + JoinThreadError(Box), } /// Dedicated [`Result`](https://doc.rust-lang.org/std/result/) type. @@ -164,10 +169,7 @@ impl VMM { Ok(()) } - pub fn configure_console( - &mut self, - console_path: Option - ) -> Result<()> { + pub fn configure_console(&mut self, console_path: Option) -> Result<()> { if let Some(console_path) = console_path { // We create the file if it does not exist, else we open let file = File::create(&console_path).map_err(Error::ConsoleError)?; @@ -226,50 +228,85 @@ impl VMM { } // Run all virtual CPUs. - pub fn run(&mut self) -> Result<()> { + pub fn run(&mut self, no_console: bool) -> Result<()> { + let mut handlers: Vec> = Vec::new(); + let should_stop = Arc::new(Mutex::new(false)); + for mut vcpu in self.vcpus.drain(..) { println!("Starting vCPU {:?}", vcpu.index); - let _ = thread::Builder::new().spawn(move || loop { - vcpu.run(); + + let should_stop_cloned = Arc::clone(&should_stop); + + let handler = thread::Builder::new().spawn(move || loop { + if *should_stop_cloned.lock().unwrap() { + println!("Stopping vCPU {:?}", vcpu.index); + break; + } + + vcpu.run(no_console, Arc::clone(&should_stop_cloned)); }); + + match handler { + Ok(handler) => handlers.push(handler), + Err(_) => { + println!("Failed to start vCPU"); + return Err(Error::AccessThreadHandlerError); + } + } } - let stdin = io::stdin(); - let stdin_lock = stdin.lock(); - stdin_lock - .set_raw_mode() - .map_err(Error::TerminalConfigure)?; - let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; - let epoll_fd = self.epoll.as_raw_fd(); - - // Let's start the STDIN polling thread. - loop { - let num_events = - epoll::wait(epoll_fd, -1, &mut events[..]).map_err(Error::EpollError)?; - - for event in events.iter().take(num_events) { - let event_data = event.data as RawFd; - - if let libc::STDIN_FILENO = event_data { - let mut out = [0u8; 64]; - - let count = stdin_lock.read_raw(&mut out).map_err(Error::StdinRead)?; - - self.serial - .lock() - .unwrap() - .serial - .enqueue_raw_bytes(&out[..count]) - .map_err(Error::StdinWrite)?; + if no_console { + for handler in handlers { + handler.join().map_err(Error::JoinThreadError)? + } + + Ok(()) + } else { + let stdin = io::stdin(); + let stdin_lock = stdin.lock(); + stdin_lock + .set_raw_mode() + .map_err(Error::TerminalConfigure)?; + let mut events = vec![epoll::Event::new(epoll::Events::empty(), 0); EPOLL_EVENTS_LEN]; + let epoll_fd = self.epoll.as_raw_fd(); + + // Let's start the STDIN polling thread. + loop { + let num_events = + epoll::wait(epoll_fd, -1, &mut events[..]).map_err(Error::EpollError)?; + + for event in events.iter().take(num_events) { + let event_data = event.data as RawFd; + + if let libc::STDIN_FILENO = event_data { + let mut out = [0u8; 64]; + + let count = stdin_lock.read_raw(&mut out).map_err(Error::StdinRead)?; + + self.serial + .lock() + .unwrap() + .serial + .enqueue_raw_bytes(&out[..count]) + .map_err(Error::StdinWrite)?; + } } } } } - pub fn configure(&mut self, num_vcpus: u8, mem_size_mb: u32, kernel_path: &str, console: Option) -> Result<()> { + pub fn configure( + &mut self, + num_vcpus: u8, + mem_size_mb: u32, + kernel_path: &str, + console: Option, + no_console: bool, + ) -> Result<()> { self.configure_console(console)?; self.configure_memory(mem_size_mb)?; - let kernel_load = kernel::kernel_setup(&self.guest_memory, PathBuf::from(kernel_path))?; + let kernel_load = + kernel::kernel_setup(&self.guest_memory, PathBuf::from(kernel_path), no_console)?; self.configure_io()?; self.configure_vcpus(num_vcpus, kernel_load)?;