diff --git a/Cargo.lock b/Cargo.lock index bb65380..735349a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,9 +144,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "linux-loader" diff --git a/src/main.rs b/src/main.rs index 02605ba..dbf551b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ fn main() -> Result<(), Error> { opts.cpus, opts.memory, &opts.kernel, - opts.console, + opts.console.clone(), opts.initramfs, opts.net, ) diff --git a/src/vmm/Cargo.lock b/src/vmm/Cargo.lock index 0623d94..89694f4 100644 --- a/src/vmm/Cargo.lock +++ b/src/vmm/Cargo.lock @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.108" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "linux-loader" diff --git a/src/vmm/src/cpu/mod.rs b/src/vmm/src/cpu/mod.rs index 3e555d9..2e07773 100644 --- a/src/vmm/src/cpu/mod.rs +++ b/src/vmm/src/cpu/mod.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause use std::convert::TryInto; -use std::io; +use std::io::Write; +use std::os::unix::net::UnixStream; use std::sync::{Arc, Mutex}; use std::{result, u64}; @@ -11,7 +12,6 @@ use kvm_ioctls::{VcpuExit, VcpuFd, VmFd}; use vm_device::bus::MmioAddress; use vm_device::device_manager::{IoManager, MmioManager}; use vm_memory::{Address, Bytes, GuestAddress, GuestMemoryError, GuestMemoryMmap}; -use vmm_sys_util::terminal::Terminal; use crate::devices::serial::{LumperSerial, SERIAL_PORT_BASE, SERIAL_PORT_LAST_REGISTER}; @@ -224,84 +224,85 @@ impl Vcpu { } /// vCPU emulation loop. - pub fn run(&mut self) { + pub fn run(&mut self, socket_name: String) { + let mut unix_socket = UnixStream::connect(socket_name).unwrap(); // 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() { - 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) }; - } + loop { + let run = self.vcpu_fd.run(); + + match run { + Ok(exit_reason) => match exit_reason { + // The VM stopped (Shutdown ot HLT). + VcpuExit::Shutdown | VcpuExit::Hlt => { + println!("Guest shutdown: {:?}. Bye!", exit_reason); + unix_socket.write_all(b"1").unwrap(); + } - // This is a PIO write, i.e. the guest is trying to write - // something to an I/O port. - VcpuExit::IoOut(addr, data) => match addr { - SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => { - self.serial - .lock() - .unwrap() - .serial - .write( + // This is a PIO write, i.e. the guest is trying to write + // something to an I/O port. + VcpuExit::IoOut(addr, data) => match addr { + SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => { + self.serial + .lock() + .unwrap() + .serial + .write( + (addr - SERIAL_PORT_BASE) + .try_into() + .expect("Invalid serial register offset"), + data[0], + ) + .unwrap(); + } + _ => { + println!("Unsupported device write at {:x?}", addr); + } + }, + + // This is a PIO read, i.e. the guest is trying to read + // from an I/O port. + VcpuExit::IoIn(addr, data) => match addr { + SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => { + data[0] = self.serial.lock().unwrap().serial.read( (addr - SERIAL_PORT_BASE) .try_into() .expect("Invalid serial register offset"), - data[0], - ) + ); + } + _ => { + println!("Unsupported device read at {:x?}", addr); + } + }, + + // This is a MMIO write, i.e. the guest is trying to write + // something to a memory-mapped I/O region. + VcpuExit::MmioWrite(addr, data) => { + self.virtio_manager + .lock() + .unwrap() + .mmio_write(MmioAddress(addr), data) .unwrap(); } - _ => { - println!("Unsupported device write at {:x?}", addr); - } - }, - // This is a PIO read, i.e. the guest is trying to read - // from an I/O port. - VcpuExit::IoIn(addr, data) => match addr { - SERIAL_PORT_BASE..=SERIAL_PORT_LAST_REGISTER => { - data[0] = self.serial.lock().unwrap().serial.read( - (addr - SERIAL_PORT_BASE) - .try_into() - .expect("Invalid serial register offset"), - ); + // This is a MMIO read, i.e. the guest is trying to read + // from a memory-mapped I/O region. + VcpuExit::MmioRead(addr, data) => { + self.virtio_manager + .lock() + .unwrap() + .mmio_read(MmioAddress(addr), data) + .unwrap(); } + _ => { - println!("Unsupported device read at {:x?}", addr); + eprintln!("Unhandled VM-Exit: {:?}", exit_reason); } }, - // This is a MMIO write, i.e. the guest is trying to write - // something to a memory-mapped I/O region. - VcpuExit::MmioWrite(addr, data) => { - self.virtio_manager - .lock() - .unwrap() - .mmio_write(MmioAddress(addr), data) - .unwrap(); - } - - // This is a MMIO read, i.e. the guest is trying to read - // from a memory-mapped I/O region. - VcpuExit::MmioRead(addr, data) => { - self.virtio_manager - .lock() - .unwrap() - .mmio_read(MmioAddress(addr), data) - .unwrap(); - } - - _ => { - eprintln!("Unhandled VM-Exit: {:?}", exit_reason); - } - }, - - Err(e) => eprintln!("Emulation error: {}", e), + Err(e) => eprintln!("Emulation error: {}", e), + } } } } diff --git a/src/vmm/src/kernel.rs b/src/vmm/src/kernel.rs index 8ad0b12..256c64e 100644 --- a/src/vmm/src/kernel.rs +++ b/src/vmm/src/kernel.rs @@ -44,7 +44,7 @@ const HIMEM_START: u64 = 0x0010_0000; // 1 MB /// Address where the kernel command line is written. const CMDLINE_START: u64 = 0x0002_0000; // Default command line -pub const DEFAULT_CMDLINE: &str = "console=ttyS0 i8042.nokbd reboot=k panic=1 pci=off"; +pub const DEFAULT_CMDLINE: &str = "i8042.nokbd reboot=k panic=1 pci=off"; fn add_e820_entry( params: &mut boot_params, diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index d7796e1..2a60d67 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -8,10 +8,13 @@ extern crate linux_loader; extern crate vm_memory; extern crate vm_superio; +use std::any::Any; use std::fs::File; use std::io::stdout; use std::os::unix::io::AsRawFd; +use std::os::unix::net::UnixListener; use std::os::unix::prelude::RawFd; +use std::path::Path; use std::sync::{Arc, Mutex}; use std::thread; use std::{io, path::PathBuf}; @@ -25,6 +28,7 @@ use vm_device::device_manager::IoManager; use vm_device::resources::Resource; use vm_memory::{Address, GuestAddress, GuestMemory, GuestMemoryMmap, GuestMemoryRegion}; use vmm_sys_util::eventfd::EventFd; +use vmm_sys_util::rand; use vmm_sys_util::terminal::Terminal; mod cpu; use cpu::{cpuid, mptable, Vcpu}; @@ -86,6 +90,10 @@ pub enum Error { VirtioNet(devices::net::VirtioNetError), /// Error related to IOManager. IoManager(vm_device::device_manager::Error), + /// Access thread handler error + AccessThreadHandlerError, + /// Join thread error + JoinThreadError(Box), } /// Dedicated [`Result`](https://doc.rust-lang.org/std/result/) type. @@ -262,6 +270,14 @@ impl VMM { } pub fn configure_console(&mut self, console_path: Option) -> Result<()> { + if console_path.is_none() { + return Ok(()); + } + + self.cmdline + .insert_str("console=ttyS0") + .map_err(Error::Cmdline)?; + 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)?; @@ -326,13 +342,41 @@ impl VMM { // Run all virtual CPUs. pub fn run(&mut self) -> Result<()> { + let mut unix_socket_name = String::from("/tmp/vmm.sock"); + while Path::new(&unix_socket_name).exists() { + let rng = rand::rand_alphanumerics(8); + unix_socket_name = format!("/tmp/vmm-{}.sock", rng.to_str().unwrap()); + } + + let mut handlers: Vec> = Vec::new(); + let listener = UnixListener::bind(unix_socket_name.as_str()).unwrap(); + let total_cpus = self.vcpus.len(); + for mut vcpu in self.vcpus.drain(..) { - println!("Starting vCPU {:?}", vcpu.index); - let _ = thread::Builder::new().spawn(move || loop { - vcpu.run(); + let socket_name = unix_socket_name.clone(); + let handler = thread::Builder::new().spawn(move || { + vcpu.run(socket_name.clone()); }); + + match handler { + Ok(handler) => handlers.push(handler), + Err(_) => { + println!("Failed to start vCPU"); + return Err(Error::AccessThreadHandlerError); + } + } + } + + let mut connections: Vec<_> = Vec::new(); + + while connections.len() < total_cpus { + let connection = listener.accept().unwrap().0; + self.epoll.add_fd(connection.as_raw_fd()).unwrap(); + connections.push(connection); } + self.epoll.add_fd(listener.as_raw_fd()).unwrap(); + let stdin = io::stdin(); let stdin_lock = stdin.lock(); stdin_lock @@ -383,6 +427,14 @@ impl VMM { .process_tap() .map_err(Error::VirtioNet)?; } + + if connections.iter().any(|c| c.as_raw_fd() == event_data) { + use vmm_sys_util::signal::Killable; + handlers.iter().for_each(|handler| { + handler.kill(9).unwrap(); + }); + return Ok(()); + } } } }