vmm: Support both reset and shutdown

Add a 2nd EventFd to the VM to control resetting (rebooting) the VM this
supplements the EventFd used for managing shutdown of the VM.

The default behaviour on i8042 or triple-fault based reset is currently
unchanged i.e. it will trigger a shutdown.

In order to support restarting the VM it was necessary to make start()
function take a reference to the config.

Signed-off-by: Rob Bradford <robert.bradford@intel.com>
This commit is contained in:
Rob Bradford 2019-08-29 14:51:45 +01:00 committed by Samuel Ortiz
parent ebe8edd423
commit ae66a44d26
3 changed files with 44 additions and 16 deletions

View File

@ -34,6 +34,7 @@ impl BusDevice for I8042Device {
fn write(&mut self, _base: u64, offset: u64, data: &[u8]) { fn write(&mut self, _base: u64, offset: u64, data: &[u8]) {
if data.len() == 1 && data[0] == 0xfe && offset == 3 { if data.len() == 1 && data[0] == 0xfe && offset == 3 {
debug!("i8042 reset signalled");
if let Err(e) = self.reset_evt.write(1) { if let Err(e) = self.reset_evt.write(1) {
error!("Error triggering i8042 reset event: {}", e); error!("Error triggering i8042 reset event: {}", e);
} }

View File

@ -15,7 +15,7 @@ pub mod config;
pub mod vm; pub mod vm;
use self::config::VmConfig; use self::config::VmConfig;
use self::vm::Vm; use self::vm::{ExitBehaviour, Vm};
/// Errors associated with VM management /// Errors associated with VM management
#[derive(Debug)] #[derive(Debug)]
@ -55,11 +55,15 @@ impl Vmm {
} }
pub fn boot_kernel(config: VmConfig) -> Result<()> { pub fn boot_kernel(config: VmConfig) -> Result<()> {
let vmm = Vmm::new()?; loop {
let mut vm = Vm::new(&vmm.kvm, config).map_err(Error::VmNew)?; let vmm = Vmm::new()?;
let mut vm = Vm::new(&vmm.kvm, &config).map_err(Error::VmNew)?;
let entry = vm.load_kernel().map_err(Error::LoadKernel)?; let entry = vm.load_kernel().map_err(Error::LoadKernel)?;
vm.start(entry).map_err(Error::VmStart)?; if vm.start(entry).map_err(Error::VmStart)? == ExitBehaviour::Shutdown {
break;
}
}
Ok(()) Ok(())
} }

View File

@ -605,9 +605,12 @@ struct DeviceManager {
serial: Option<Arc<Mutex<devices::legacy::Serial>>>, serial: Option<Arc<Mutex<devices::legacy::Serial>>>,
console_input: Option<Arc<vm_virtio::ConsoleInput>>, console_input: Option<Arc<vm_virtio::ConsoleInput>>,
// i8042 device for exit // i8042 device for i8042 reset
i8042: Arc<Mutex<devices::legacy::I8042Device>>, i8042: Arc<Mutex<devices::legacy::I8042Device>>,
// Shutdown (exit) and reboot (reset) control
exit_evt: EventFd, exit_evt: EventFd,
reset_evt: EventFd,
// IOAPIC // IOAPIC
ioapic: Option<Arc<Mutex<ioapic::Ioapic>>>, ioapic: Option<Arc<Mutex<ioapic::Ioapic>>>,
@ -679,6 +682,7 @@ impl DeviceManager {
// Add a shutdown device (i8042) // Add a shutdown device (i8042)
let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(DeviceManagerError::EventFd)?; let exit_evt = EventFd::new(EFD_NONBLOCK).map_err(DeviceManagerError::EventFd)?;
let reset_evt = EventFd::new(EFD_NONBLOCK).map_err(DeviceManagerError::EventFd)?;
let i8042 = Arc::new(Mutex::new(devices::legacy::I8042Device::new( let i8042 = Arc::new(Mutex::new(devices::legacy::I8042Device::new(
exit_evt.try_clone().map_err(DeviceManagerError::EventFd)?, exit_evt.try_clone().map_err(DeviceManagerError::EventFd)?,
))); )));
@ -734,6 +738,7 @@ impl DeviceManager {
console_input: console, console_input: console,
i8042, i8042,
exit_evt, exit_evt,
reset_evt,
ioapic, ioapic,
pci, pci,
}) })
@ -1298,6 +1303,7 @@ impl DeviceManager {
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
enum EpollDispatch { enum EpollDispatch {
Exit, Exit,
Reset,
Stdin, Stdin,
} }
@ -1313,7 +1319,7 @@ impl EpollContext {
// Initial capacity needs to be large enough to hold: // Initial capacity needs to be large enough to hold:
// * 1 exit event // * 1 exit event
// * 1 stdin event // * 1 stdin event
let mut dispatch_table = Vec::with_capacity(3); let mut dispatch_table = Vec::with_capacity(4);
dispatch_table.push(None); dispatch_table.push(None);
Ok(EpollContext { Ok(EpollContext {
@ -1359,6 +1365,12 @@ impl AsRawFd for EpollContext {
} }
} }
#[derive(PartialEq)]
pub enum ExitBehaviour {
Shutdown = 1,
Reset = 2,
}
pub struct Vm<'a> { pub struct Vm<'a> {
fd: Arc<VmFd>, fd: Arc<VmFd>,
kernel: File, kernel: File,
@ -1366,14 +1378,14 @@ pub struct Vm<'a> {
vcpus: Vec<thread::JoinHandle<()>>, vcpus: Vec<thread::JoinHandle<()>>,
devices: DeviceManager, devices: DeviceManager,
cpuid: CpuId, cpuid: CpuId,
config: VmConfig<'a>, config: &'a VmConfig<'a>,
epoll: EpollContext, epoll: EpollContext,
on_tty: bool, on_tty: bool,
creation_ts: std::time::Instant, creation_ts: std::time::Instant,
} }
impl<'a> Vm<'a> { impl<'a> Vm<'a> {
pub fn new(kvm: &Kvm, config: VmConfig<'a>) -> Result<Self> { pub fn new(kvm: &Kvm, config: &'a VmConfig<'a>) -> Result<Self> {
let kernel = File::open(&config.kernel.path).map_err(Error::KernelFile)?; let kernel = File::open(&config.kernel.path).map_err(Error::KernelFile)?;
let fd = kvm.create_vm().map_err(Error::VmCreate)?; let fd = kvm.create_vm().map_err(Error::VmCreate)?;
let fd = Arc::new(fd); let fd = Arc::new(fd);
@ -1553,7 +1565,7 @@ impl<'a> Vm<'a> {
let vm_info = VmInfo { let vm_info = VmInfo {
memory: &guest_memory, memory: &guest_memory,
vm_fd: &fd, vm_fd: &fd,
vm_cfg: &config, vm_cfg: config,
}; };
let device_manager = DeviceManager::new( let device_manager = DeviceManager::new(
@ -1577,6 +1589,9 @@ impl<'a> Vm<'a> {
epoll epoll
.add_event(&device_manager.exit_evt, EpollDispatch::Exit) .add_event(&device_manager.exit_evt, EpollDispatch::Exit)
.map_err(Error::EpollError)?; .map_err(Error::EpollError)?;
epoll
.add_event(&device_manager.reset_evt, EpollDispatch::Reset)
.map_err(Error::EpollError)?;
let vcpus = Vec::with_capacity(u8::from(&config.cpus) as usize); let vcpus = Vec::with_capacity(u8::from(&config.cpus) as usize);
@ -1662,7 +1677,7 @@ impl<'a> Vm<'a> {
} }
} }
pub fn control_loop(&mut self) -> Result<()> { pub fn control_loop(&mut self) -> Result<ExitBehaviour> {
// Let's start the STDIN polling thread. // Let's start the STDIN polling thread.
const EPOLL_EVENTS_LEN: usize = 100; const EPOLL_EVENTS_LEN: usize = 100;
@ -1676,6 +1691,8 @@ impl<'a> Vm<'a> {
.map_err(Error::SetTerminalRaw)?; .map_err(Error::SetTerminalRaw)?;
} }
let exit_behaviour;
'outer: loop { 'outer: loop {
let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) { let num_events = match epoll::wait(epoll_fd, -1, &mut events[..]) {
Ok(res) => res, Ok(res) => res,
@ -1702,6 +1719,14 @@ impl<'a> Vm<'a> {
EpollDispatch::Exit => { EpollDispatch::Exit => {
// Consume the event. // Consume the event.
self.devices.exit_evt.read().map_err(Error::EventFd)?; self.devices.exit_evt.read().map_err(Error::EventFd)?;
exit_behaviour = ExitBehaviour::Shutdown;
break 'outer;
}
EpollDispatch::Reset => {
// Consume the event.
self.devices.reset_evt.read().map_err(Error::EventFd)?;
exit_behaviour = ExitBehaviour::Reset;
break 'outer; break 'outer;
} }
@ -1749,7 +1774,7 @@ impl<'a> Vm<'a> {
.map_err(Error::SetTerminalCanon)?; .map_err(Error::SetTerminalCanon)?;
} }
Ok(()) Ok(exit_behaviour)
} }
fn os_signal_handler(signals: Signals, console_input_clone: Arc<vm_virtio::ConsoleInput>) { fn os_signal_handler(signals: Signals, console_input_clone: Arc<vm_virtio::ConsoleInput>) {
@ -1761,7 +1786,7 @@ impl<'a> Vm<'a> {
} }
} }
pub fn start(&mut self, entry_addr: GuestAddress) -> Result<()> { pub fn start(&mut self, entry_addr: GuestAddress) -> Result<ExitBehaviour> {
self.devices.register_devices()?; self.devices.register_devices()?;
let vcpu_count = u8::from(&self.config.cpus); let vcpu_count = u8::from(&self.config.cpus);
@ -1824,9 +1849,7 @@ impl<'a> Vm<'a> {
Err(e) => error!("Signal not found {}", e), Err(e) => error!("Signal not found {}", e),
} }
} }
self.control_loop()?; self.control_loop()
Ok(())
} }
/// Gets an Arc to the guest memory owned by this VM. /// Gets an Arc to the guest memory owned by this VM.