crosvm: vm_events: consolidate vm events into one framework.

crosvm waits on events like exit, reset, crash, guest panic etc and
uses eventfd to wait on these events. As of now, we have 4 eventfds
and may increase.

This is an attempt to consolidate all Vm events into one framework.
Use Tube instead of Event to get consistent behavior between OSes.
Implement a wrapper over Tube to have a consistent API for events.

BUG=None.
TEST=Built crosvm. Ran a minimal vm to panic and verified that crosvm
received the panic event. cargo test on devices.

Change-Id: I313d428de5e3ce3b879982f913918ec0a4a72c35
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3480577
Tested-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Commit-Queue: Vineeth Pillai <vineethrp@google.com>
Reviewed-by: Noah Gold <nkgold@google.com>
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index 18687af..daba095 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -14,7 +14,7 @@
     get_serial_cmdline, GetSerialCmdlineError, MsrConfig, MsrExitHandlerError, RunnableLinuxVm,
     VmComponents, VmImage,
 };
-use base::{Event, MemoryMappingBuilder};
+use base::{Event, MemoryMappingBuilder, SendTube, Tube};
 use devices::serial_device::{SerialHardware, SerialParameters};
 use devices::{
     Bus, BusDeviceObj, BusError, IrqChip, IrqChipAArch64, PciAddress, PciConfigMmio, PciDevice,
@@ -232,8 +232,7 @@
 
     fn build_vm<V, Vcpu>(
         mut components: VmComponents,
-        _exit_evt: &Event,
-        _reset_evt: &Event,
+        _vm_evt_wrtube: &SendTube,
         system_allocator: &mut SystemAllocator,
         serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
         serial_jail: Option<Minijail>,
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index f91c779..ea2fbf6 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -20,7 +20,7 @@
 
 use acpi_tables::aml::Aml;
 use acpi_tables::sdt::SDT;
-use base::{syslog, AsRawDescriptor, AsRawDescriptors, Event, Tube};
+use base::{syslog, AsRawDescriptor, AsRawDescriptors, Event, SendTube, Tube};
 use devices::virtio::VirtioDevice;
 use devices::{
     BarRange, Bus, BusDevice, BusDeviceObj, BusError, BusResumeDevice, HotPlugBus, IrqChip,
@@ -184,10 +184,8 @@
     /// # Arguments
     ///
     /// * `components` - Parts to use to build the VM.
-    /// * `exit_evt` - Event used by sub-devices to request that crosvm exit because guest
-    ///     wants to stop/shut down.
-    /// * `reset_evt` - Event used by sub-devices to request that crosvm exit because guest
-    ///     requested reset.
+    /// * `vm_evt_wrtube` - Tube used by sub-devices to request that crosvm exit because guest
+    ///     wants to stop/shut down or requested reset.
     /// * `system_allocator` - Allocator created by this trait's implementation of
     ///   `get_system_allocator_config`.
     /// * `serial_parameters` - Definitions for how the serial devices should be configured.
@@ -199,8 +197,7 @@
     /// * `irq_chip` - The IRQ chip implemention for the VM.
     fn build_vm<V, Vcpu>(
         components: VmComponents,
-        exit_evt: &Event,
-        reset_evt: &Event,
+        vm_evt_wrtube: &SendTube,
         system_allocator: &mut SystemAllocator,
         serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
         serial_jail: Option<Minijail>,
diff --git a/base/src/lib.rs b/base/src/lib.rs
index fd941da..7f1d940 100644
--- a/base/src/lib.rs
+++ b/base/src/lib.rs
@@ -146,3 +146,12 @@
         .encode_lower(&mut buf)
         .to_owned()
 }
+
+use serde::{Deserialize, Serialize};
+#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
+pub enum VmEventType {
+    Exit,
+    Reset,
+    Crash,
+    Panic(u8),
+}
diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs
index 46143b4..5ce06c5 100644
--- a/devices/src/acpi.rs
+++ b/devices/src/acpi.rs
@@ -4,7 +4,9 @@
 
 use crate::{BusAccessInfo, BusDevice, BusResumeDevice, IrqLevelEvent};
 use acpi_tables::{aml, aml::Aml};
-use base::{error, info, warn, Error as SysError, Event, PollToken, WaitContext};
+use base::{
+    error, info, warn, Error as SysError, Event, PollToken, SendTube, VmEventType, WaitContext,
+};
 use base::{AcpiNotifyEvent, NetlinkGenericSocket};
 use std::collections::BTreeMap;
 use std::sync::Arc;
@@ -63,7 +65,7 @@
     kill_evt: Option<Event>,
     worker_thread: Option<thread::JoinHandle<()>>,
     suspend_evt: Event,
-    exit_evt: Event,
+    exit_evt_wrtube: SendTube,
     pm1: Arc<Mutex<Pm1Resource>>,
     gpe0: Arc<Mutex<GpeResource>>,
 }
@@ -77,7 +79,7 @@
         sci_evt: IrqLevelEvent,
         #[cfg(feature = "direct")] direct_gpe_info: Option<(IrqLevelEvent, &[u32])>,
         suspend_evt: Event,
-        exit_evt: Event,
+        exit_evt_wrtube: SendTube,
     ) -> ACPIPMResource {
         let pm1 = Pm1Resource {
             status: 0,
@@ -108,7 +110,7 @@
             kill_evt: None,
             worker_thread: None,
             suspend_evt,
-            exit_evt,
+            exit_evt_wrtube,
             pm1: Arc::new(Mutex::new(pm1)),
             gpe0: Arc::new(Mutex::new(gpe0)),
         }
@@ -777,7 +779,7 @@
                 if (val & BITMASK_PM1CNT_SLEEP_ENABLE) != 0 {
                     // only support S5 in direct mode
                     #[cfg(feature = "direct")]
-                    if let Err(e) = self.exit_evt.write(1) {
+                    if let Err(e) = self.exit_evt_wrtube.send::<VmEventType>(&VmEventType::Exit) {
                         error!("ACPIPM: failed to trigger exit event: {}", e);
                     }
                     #[cfg(not(feature = "direct"))]
@@ -788,7 +790,9 @@
                             }
                         }
                         SLEEP_TYPE_S5 => {
-                            if let Err(e) = self.exit_evt.write(1) {
+                            if let Err(e) =
+                                self.exit_evt_wrtube.send::<VmEventType>(&VmEventType::Exit)
+                            {
                                 error!("ACPIPM: failed to trigger exit event: {}", e);
                             }
                         }
diff --git a/devices/src/i8042.rs b/devices/src/i8042.rs
index 92ea2a1..4085476 100644
--- a/devices/src/i8042.rs
+++ b/devices/src/i8042.rs
@@ -2,19 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use base::{error, Event};
+use base::{error, SendTube, VmEventType};
 
 use crate::{BusAccessInfo, BusDevice};
 
 /// A i8042 PS/2 controller that emulates just enough to shutdown the machine.
 pub struct I8042Device {
-    reset_evt: Event,
+    reset_evt_wrtube: SendTube,
 }
 
 impl I8042Device {
     /// Constructs a i8042 device that will signal the given event when the guest requests it.
-    pub fn new(reset_evt: Event) -> I8042Device {
-        I8042Device { reset_evt }
+    pub fn new(reset_evt_wrtube: SendTube) -> I8042Device {
+        I8042Device { reset_evt_wrtube }
     }
 }
 
@@ -37,7 +37,10 @@
 
     fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
         if data.len() == 1 && data[0] == 0xfe && info.address == 0x64 {
-            if let Err(e) = self.reset_evt.write(1) {
+            if let Err(e) = self
+                .reset_evt_wrtube
+                .send::<VmEventType>(&VmEventType::Reset)
+            {
                 error!("failed to trigger i8042 reset event: {}", e);
             }
         }
diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs
index 517964e..7da5ee0 100644
--- a/devices/src/pci/pci_root.rs
+++ b/devices/src/pci/pci_root.rs
@@ -7,7 +7,7 @@
 use std::ops::Bound::Included;
 use std::sync::{Arc, Weak};
 
-use base::{error, Event, RawDescriptor};
+use base::{error, RawDescriptor, SendTube, VmEventType};
 use sync::Mutex;
 
 use crate::pci::pci_configuration::{
@@ -248,18 +248,18 @@
     pci_root: Arc<Mutex<PciRoot>>,
     /// Current address to read/write from (0xcf8 register, litte endian).
     config_address: u32,
-    /// Event to signal that the quest requested reset via writing to 0xcf9 register.
-    reset_evt: Event,
+    /// Tube to signal that the guest requested reset via writing to 0xcf9 register.
+    reset_evt_wrtube: SendTube,
 }
 
 impl PciConfigIo {
     const REGISTER_BITS_NUM: usize = 8;
 
-    pub fn new(pci_root: Arc<Mutex<PciRoot>>, reset_evt: Event) -> Self {
+    pub fn new(pci_root: Arc<Mutex<PciRoot>>, reset_evt_wrtube: SendTube) -> Self {
         PciConfigIo {
             pci_root,
             config_address: 0,
-            reset_evt,
+            reset_evt_wrtube,
         }
     }
 
@@ -344,7 +344,10 @@
         // `offset` is relative to 0xcf8
         match info.offset {
             _o @ 1 if data.len() == 1 && data[0] & PCI_RESET_CPU_BIT != 0 => {
-                if let Err(e) = self.reset_evt.write(1) {
+                if let Err(e) = self
+                    .reset_evt_wrtube
+                    .send::<VmEventType>(&VmEventType::Reset)
+                {
                     error!("failed to trigger PCI 0xcf9 reset event: {}", e);
                 }
             }
diff --git a/devices/src/pci/pvpanic.rs b/devices/src/pci/pvpanic.rs
index 5265a9f..bac36ee 100644
--- a/devices/src/pci/pvpanic.rs
+++ b/devices/src/pci/pvpanic.rs
@@ -15,7 +15,7 @@
 use std::fmt;
 
 use base::RawDescriptor;
-use base::{self, error, Tube};
+use base::{self, error, SendTube, VmEventType};
 use resources::{Alloc, MmioType, SystemAllocator};
 
 use crate::pci::pci_configuration::{
@@ -69,11 +69,11 @@
 pub struct PvPanicPciDevice {
     pci_address: Option<PciAddress>,
     config_regs: PciConfiguration,
-    evt_tube: Tube,
+    evt_wrtube: SendTube,
 }
 
 impl PvPanicPciDevice {
-    pub fn new(evt_tube: Tube) -> PvPanicPciDevice {
+    pub fn new(evt_wrtube: SendTube) -> PvPanicPciDevice {
         let config_regs = PciConfiguration::new(
             PCI_VENDOR_ID_REDHAT,
             PCI_DEVICE_ID_REDHAT_PVPANIC,
@@ -89,7 +89,7 @@
         Self {
             pci_address: None,
             config_regs,
-            evt_tube,
+            evt_wrtube,
         }
     }
 }
@@ -182,8 +182,12 @@
         if addr != mmio_addr || data.len() != 1 {
             return;
         }
-        if let Err(e) = self.evt_tube.send::<u8>(&data[0]) {
-            error!("Failed to send to the panic event: {}", e);
+
+        if let Err(e) = self
+            .evt_wrtube
+            .send::<VmEventType>(&VmEventType::Panic(data[0]))
+        {
+            error!("Failed to write to the event tube: {}", e);
         }
     }
 }
@@ -191,6 +195,7 @@
 #[cfg(test)]
 mod test {
     use super::*;
+    use base::Tube;
     use resources::{MemRegion, SystemAllocator, SystemAllocatorConfig};
 
     #[test]
@@ -217,7 +222,7 @@
         )
         .unwrap();
 
-        let (evt_rdtube, evt_wrtube) = Tube::pair().unwrap();
+        let (evt_wrtube, evt_rdtube) = Tube::directional_pair().unwrap();
         let mut device = PvPanicPciDevice::new(evt_wrtube);
 
         assert!(device.allocate_address(&mut allocator).is_ok());
@@ -239,7 +244,7 @@
         device.write_bar(mmio_addr, &data);
 
         // Verify the event
-        let val = evt_rdtube.recv::<u8>().unwrap();
-        assert_eq!(val, PVPANIC_CRASH_LOADED);
+        let val = evt_rdtube.recv::<VmEventType>().unwrap();
+        assert_eq!(val, VmEventType::Panic(PVPANIC_CRASH_LOADED));
     }
 }
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index fba1925..827a3cb 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -23,7 +23,7 @@
 
 use base::{
     debug, error, warn, AsRawDescriptor, Event, ExternalMapping, PollToken, RawDescriptor,
-    SafeDescriptor, Tube, WaitContext,
+    SafeDescriptor, SendTube, Tube, VmEventType, WaitContext,
 };
 
 use data_model::*;
@@ -795,7 +795,7 @@
 
 struct Worker {
     interrupt: Arc<Interrupt>,
-    exit_evt: Event,
+    exit_evt_wrtube: SendTube,
     mem: GuestMemory,
     ctrl_queue: SharedQueueReader,
     ctrl_evt: Event,
@@ -914,7 +914,7 @@
                     Token::Display => {
                         let close_requested = self.state.process_display();
                         if close_requested {
-                            let _ = self.exit_evt.write(1);
+                            let _ = self.exit_evt_wrtube.send::<VmEventType>(&VmEventType::Exit);
                         }
                     }
                     Token::ResourceBridge { index } => {
@@ -1002,7 +1002,7 @@
 }
 
 pub struct Gpu {
-    exit_evt: Event,
+    exit_evt_wrtube: SendTube,
     gpu_device_tube: Option<Tube>,
     resource_bridges: Vec<Tube>,
     event_devices: Vec<EventDevice>,
@@ -1023,7 +1023,7 @@
 
 impl Gpu {
     pub fn new(
-        exit_evt: Event,
+        exit_evt_wrtube: SendTube,
         gpu_device_tube: Option<Tube>,
         resource_bridges: Vec<Tube>,
         display_backends: Vec<DisplayBackend>,
@@ -1090,7 +1090,7 @@
             .set_rutabaga_channels(rutabaga_channels_opt);
 
         Gpu {
-            exit_evt,
+            exit_evt_wrtube,
             gpu_device_tube,
             resource_bridges,
             event_devices,
@@ -1215,7 +1215,7 @@
             keep_rds.push(render_server_fd.as_raw_descriptor());
         }
 
-        keep_rds.push(self.exit_evt.as_raw_descriptor());
+        keep_rds.push(self.exit_evt_wrtube.as_raw_descriptor());
         for bridge in &self.resource_bridges {
             keep_rds.push(bridge.as_raw_descriptor());
         }
@@ -1281,10 +1281,10 @@
             return;
         }
 
-        let exit_evt = match self.exit_evt.try_clone() {
+        let exit_evt_wrtube = match self.exit_evt_wrtube.try_clone() {
             Ok(e) => e,
             Err(e) => {
-                error!("error cloning exit event: {}", e);
+                error!("error cloning exit tube: {}", e);
                 return;
             }
         };
@@ -1347,7 +1347,7 @@
 
                         Worker {
                             interrupt: irq,
-                            exit_evt,
+                            exit_evt_wrtube,
                             mem,
                             ctrl_queue: ctrl_queue.clone(),
                             ctrl_evt,
diff --git a/devices/src/virtio/vhost/user/device/gpu.rs b/devices/src/virtio/vhost/user/device/gpu.rs
index b09e4b6..3a90d93 100644
--- a/devices/src/virtio/vhost/user/device/gpu.rs
+++ b/devices/src/virtio/vhost/user/device/gpu.rs
@@ -439,7 +439,11 @@
         .detach();
     }
 
-    let exit_evt = Event::new().context("failed to create Event")?;
+    // TODO(b/232344535): Read side of the tube is ignored currently.
+    // Complete the implementation by polling `exit_evt_rdtube` and
+    // kill the sibling VM.
+    let (exit_evt_wrtube, _) =
+        Tube::directional_pair().context("failed to create vm event tube")?;
 
     // Initialized later.
     let gpu_device_tube = None;
@@ -465,7 +469,7 @@
     let channels = wayland_paths;
 
     let gpu = Rc::new(RefCell::new(Gpu::new(
-        exit_evt,
+        exit_evt_wrtube,
         gpu_device_tube,
         Vec::new(), // resource_bridges, handled separately by us
         display_backends,
diff --git a/src/linux/gpu.rs b/src/linux/gpu.rs
index 7fe860a..f16c9c9 100644
--- a/src/linux/gpu.rs
+++ b/src/linux/gpu.rs
@@ -153,7 +153,7 @@
 
 pub fn create_gpu_device(
     cfg: &Config,
-    exit_evt: &Event,
+    exit_evt_wrtube: &SendTube,
     gpu_device_tube: Tube,
     resource_bridges: Vec<Tube>,
     wayland_socket_path: Option<&PathBuf>,
@@ -182,7 +182,9 @@
     }
 
     let dev = virtio::Gpu::new(
-        exit_evt.try_clone().context("failed to clone event")?,
+        exit_evt_wrtube
+            .try_clone()
+            .context("failed to clone tube")?,
         Some(gpu_device_tube),
         resource_bridges,
         display_backends,
diff --git a/src/linux/mod.rs b/src/linux/mod.rs
index d5f3ec5..7cf99dc 100644
--- a/src/linux/mod.rs
+++ b/src/linux/mod.rs
@@ -98,7 +98,7 @@
     cfg: &Config,
     vm: &mut impl Vm,
     resources: &mut SystemAllocator,
-    _exit_evt: &Event,
+    vm_evt_wrtube: &SendTube,
     wayland_device_tube: Tube,
     gpu_device_tube: Tube,
     vhost_user_gpu_tubes: Vec<(Tube, Tube, Tube)>,
@@ -233,7 +233,7 @@
 
             devs.push(create_gpu_device(
                 cfg,
-                _exit_evt,
+                vm_evt_wrtube,
                 gpu_device_tube,
                 resource_bridges,
                 // Use the unnamed socket for GPU display screens.
@@ -461,8 +461,7 @@
     cfg: &Config,
     vm: &mut impl Vm,
     resources: &mut SystemAllocator,
-    exit_evt: &Event,
-    panic_wrtube: Tube,
+    vm_evt_wrtube: &SendTube,
     iommu_attached_endpoints: &mut BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
     control_tubes: &mut Vec<TaggedControlTube>,
     wayland_device_tube: Tube,
@@ -585,7 +584,7 @@
         cfg,
         vm,
         resources,
-        exit_evt,
+        vm_evt_wrtube,
         wayland_device_tube,
         gpu_device_tube,
         vhost_user_gpu_tubes,
@@ -631,7 +630,11 @@
         devices.push((Box::new(StubPciDevice::new(params)), None));
     }
 
-    devices.push((Box::new(PvPanicPciDevice::new(panic_wrtube)), None));
+    devices.push((
+        Box::new(PvPanicPciDevice::new(vm_evt_wrtube.try_clone()?)),
+        None,
+    ));
+
     Ok(devices)
 }
 
@@ -1218,10 +1221,8 @@
         vvu_proxy_device_tubes.push(vvu_proxy_device_tube);
     }
 
-    let exit_evt = Event::new().context("failed to create event")?;
-    let reset_evt = Event::new().context("failed to create event")?;
-    let crash_evt = Event::new().context("failed to create event")?;
-    let (panic_rdtube, panic_wrtube) = Tube::pair().context("failed to create tube")?;
+    let (vm_evt_wrtube, vm_evt_rdtube) =
+        Tube::directional_pair().context("failed to create vm event tube")?;
 
     let pstore_size = components.pstore.as_ref().map(|pstore| pstore.size as u64);
     let mut sys_allocator = SystemAllocator::new(
@@ -1318,8 +1319,7 @@
         &cfg,
         &mut vm,
         &mut sys_allocator,
-        &exit_evt,
-        panic_wrtube,
+        &vm_evt_wrtube,
         &mut iommu_attached_endpoints,
         &mut control_tubes,
         wayland_device_tube,
@@ -1418,8 +1418,7 @@
     #[cfg_attr(not(feature = "direct"), allow(unused_mut))]
     let mut linux = Arch::build_vm::<V, Vcpu>(
         components,
-        &exit_evt,
-        &reset_evt,
+        &vm_evt_wrtube,
         &mut sys_allocator,
         &cfg.serial_parameters,
         simple_jail(&cfg.jail_config, "serial")?,
@@ -1484,10 +1483,8 @@
         &disk_host_tubes,
         #[cfg(feature = "usb")]
         usb_control_tube,
-        exit_evt,
-        reset_evt,
-        crash_evt,
-        panic_rdtube,
+        vm_evt_rdtube,
+        vm_evt_wrtube,
         sigchld_fd,
         Arc::clone(&map_request),
         gralloc,
@@ -1669,10 +1666,8 @@
     balloon_host_tube: Option<Tube>,
     disk_host_tubes: &[Tube],
     #[cfg(feature = "usb")] usb_control_tube: Tube,
-    exit_evt: Event,
-    reset_evt: Event,
-    crash_evt: Event,
-    panic_rdtube: Tube,
+    vm_evt_rdtube: RecvTube,
+    vm_evt_wrtube: SendTube,
     sigchld_fd: SignalFd,
     map_request: Arc<Mutex<Option<ExternalMapping>>>,
     mut gralloc: RutabagaGralloc,
@@ -1681,10 +1676,7 @@
 ) -> Result<ExitState> {
     #[derive(PollToken)]
     enum Token {
-        Exit,
-        Reset,
-        Crash,
-        Panic,
+        VmEvent,
         Suspend,
         ChildSignal,
         IrqFd { index: IrqEventIndex },
@@ -1698,12 +1690,9 @@
         .expect("failed to set terminal raw mode");
 
     let wait_ctx = WaitContext::build_with(&[
-        (&exit_evt, Token::Exit),
-        (&reset_evt, Token::Reset),
-        (&crash_evt, Token::Crash),
-        (&panic_rdtube, Token::Panic),
         (&linux.suspend_evt, Token::Suspend),
         (&sigchld_fd, Token::ChildSignal),
+        (&vm_evt_rdtube, Token::VmEvent),
     ])
     .context("failed to add descriptor to wait context")?;
 
@@ -1808,9 +1797,9 @@
             linux.has_bios,
             (*linux.io_bus).clone(),
             (*linux.mmio_bus).clone(),
-            exit_evt.try_clone().context("failed to clone event")?,
-            reset_evt.try_clone().context("failed to clone event")?,
-            crash_evt.try_clone().context("failed to clone event")?,
+            vm_evt_wrtube
+                .try_clone()
+                .context("failed to clone vm event tube")?,
             linux.vm.check_capability(VmCap::PvClockSuspend),
             from_main_channel,
             use_hypervisor_signals,
@@ -1869,37 +1858,38 @@
         let mut vm_control_indices_to_remove = Vec::new();
         for event in events.iter().filter(|e| e.is_readable) {
             match event.token {
-                Token::Exit => {
-                    info!("vcpu requested shutdown");
-                    break 'wait;
-                }
-                Token::Reset => {
-                    info!("vcpu requested reset");
-                    exit_state = ExitState::Reset;
-                    break 'wait;
-                }
-                Token::Crash => {
-                    info!("vcpu crashed");
-                    exit_state = ExitState::Crash;
-                    break 'wait;
-                }
-                Token::Panic => {
+                Token::VmEvent => {
                     let mut break_to_wait: bool = true;
-                    match panic_rdtube.recv::<u8>() {
-                        Ok(panic_code) => {
-                            let panic_code = PvPanicCode::from_u8(panic_code);
-                            info!("Guest reported panic [Code: {}]", panic_code);
-                            if panic_code == PvPanicCode::CrashLoaded {
-                                // VM is booting to crash kernel.
-                                break_to_wait = false;
+                    match vm_evt_rdtube.recv::<VmEventType>() {
+                        Ok(vm_event) => match vm_event {
+                            VmEventType::Exit => {
+                                info!("vcpu requested shutdown");
+                                exit_state = ExitState::Stop;
                             }
-                        }
+                            VmEventType::Reset => {
+                                info!("vcpu requested reset");
+                                exit_state = ExitState::Reset;
+                            }
+                            VmEventType::Crash => {
+                                info!("vcpu crashed");
+                                exit_state = ExitState::Crash;
+                            }
+                            VmEventType::Panic(panic_code) => {
+                                let panic_code = PvPanicCode::from_u8(panic_code);
+                                info!("Guest reported panic [Code: {}]", panic_code);
+                                if panic_code == PvPanicCode::CrashLoaded {
+                                    // VM is booting to crash kernel.
+                                    break_to_wait = false;
+                                } else {
+                                    exit_state = ExitState::GuestPanic;
+                                }
+                            }
+                        },
                         Err(e) => {
-                            warn!("failed to recv panic event: {} ", e);
+                            warn!("failed to recv VmEvent: {}", e);
                         }
                     }
                     if break_to_wait {
-                        exit_state = ExitState::GuestPanic;
                         break 'wait;
                     }
                 }
diff --git a/src/linux/vcpu.rs b/src/linux/vcpu.rs
index 5dce07c..798ac8e 100644
--- a/src/linux/vcpu.rs
+++ b/src/linux/vcpu.rs
@@ -535,9 +535,7 @@
     has_bios: bool,
     mut io_bus: Bus,
     mut mmio_bus: Bus,
-    exit_evt: Event,
-    reset_evt: Event,
-    crash_evt: Event,
+    vm_evt_wrtube: SendTube,
     requires_pvclock_ctrl: bool,
     from_main_tube: mpsc::Receiver<VcpuControl>,
     use_hypervisor_signals: bool,
@@ -557,100 +555,97 @@
     thread::Builder::new()
         .name(format!("crosvm_vcpu{}", cpu_id))
         .spawn(move || {
-            // The VCPU thread must trigger either `exit_evt` or `reset_event` in all paths. A
-            // `ScopedEvent`'s Drop implementation ensures that the `exit_evt` will be sent if
-            // anything happens before we get to writing the final event.
-            let scoped_exit_evt = ScopedEvent::from(exit_evt);
+            // Having a closure returning ExitState guarentees that we
+            // send a VmEventType on all code paths after the closure
+            // returns.
+            let vcpu_fn = || -> ExitState {
+                #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+                let guest_mem = vm.get_memory().clone();
+                let runnable_vcpu = runnable_vcpu(
+                    cpu_id,
+                    vcpu_id,
+                    vcpu,
+                    vm,
+                    irq_chip.as_mut(),
+                    vcpu_count,
+                    run_rt && !delay_rt,
+                    vcpu_affinity,
+                    no_smt,
+                    has_bios,
+                    use_hypervisor_signals,
+                    enable_per_vm_core_scheduling,
+                    host_cpu_topology,
+                    itmt,
+                    vcpu_cgroup_tasks_file,
+                );
 
-            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-            let guest_mem = vm.get_memory().clone();
-            let runnable_vcpu = runnable_vcpu(
-                cpu_id,
-                vcpu_id,
-                vcpu,
-                vm,
-                irq_chip.as_mut(),
-                vcpu_count,
-                run_rt && !delay_rt,
-                vcpu_affinity,
-                no_smt,
-                has_bios,
-                use_hypervisor_signals,
-                enable_per_vm_core_scheduling,
-                host_cpu_topology,
-                itmt,
-                vcpu_cgroup_tasks_file,
-            );
-
-            // Add MSR handlers after CPU affinity setting.
-            // This avoids redundant MSR file fd creation.
-            let mut msr_handlers = MsrHandlers::new();
-            if !userspace_msr.is_empty() {
-                userspace_msr.iter().for_each(|(index, msr_config)| {
-                    if let Err(e) = msr_handlers.add_handler(*index, msr_config.clone(), cpu_id) {
-                        error!("failed to add msr handler {}: {:#}", cpu_id, e);
-                        return;
-                    };
-                });
-            }
-
-            start_barrier.wait();
-
-            let (vcpu, vcpu_run_handle) = match runnable_vcpu {
-                Ok(v) => v,
-                Err(e) => {
-                    error!("failed to start vcpu {}: {:#}", cpu_id, e);
-                    return;
+                // Add MSR handlers after CPU affinity setting.
+                // This avoids redundant MSR file fd creation.
+                let mut msr_handlers = MsrHandlers::new();
+                if !userspace_msr.is_empty() {
+                    userspace_msr.iter().for_each(|(index, msr_config)| {
+                        if let Err(e) = msr_handlers.add_handler(*index, msr_config.clone(), cpu_id)
+                        {
+                            error!("failed to add msr handler {}: {:#}", cpu_id, e);
+                        };
+                    });
                 }
+
+                start_barrier.wait();
+
+                let (vcpu, vcpu_run_handle) = match runnable_vcpu {
+                    Ok(v) => v,
+                    Err(e) => {
+                        error!("failed to start vcpu {}: {:#}", cpu_id, e);
+                        return ExitState::Stop;
+                    }
+                };
+
+                #[allow(unused_mut)]
+                let mut run_mode = VmRunMode::Running;
+                #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+                if to_gdb_tube.is_some() {
+                    // Wait until a GDB client attaches
+                    run_mode = VmRunMode::Breakpoint;
+                }
+
+                mmio_bus.set_access_id(cpu_id);
+                io_bus.set_access_id(cpu_id);
+
+                vcpu_loop(
+                    run_mode,
+                    cpu_id,
+                    vcpu,
+                    vcpu_run_handle,
+                    irq_chip,
+                    run_rt,
+                    delay_rt,
+                    io_bus,
+                    mmio_bus,
+                    requires_pvclock_ctrl,
+                    from_main_tube,
+                    use_hypervisor_signals,
+                    privileged_vm,
+                    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+                    to_gdb_tube,
+                    #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+                    guest_mem,
+                    msr_handlers,
+                )
             };
 
-            #[allow(unused_mut)]
-            let mut run_mode = VmRunMode::Running;
-            #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-            if to_gdb_tube.is_some() {
-                // Wait until a GDB client attaches
-                run_mode = VmRunMode::Breakpoint;
-            }
-
-            mmio_bus.set_access_id(cpu_id);
-            io_bus.set_access_id(cpu_id);
-
-            let exit_reason = vcpu_loop(
-                run_mode,
-                cpu_id,
-                vcpu,
-                vcpu_run_handle,
-                irq_chip,
-                run_rt,
-                delay_rt,
-                io_bus,
-                mmio_bus,
-                requires_pvclock_ctrl,
-                from_main_tube,
-                use_hypervisor_signals,
-                privileged_vm,
-                #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-                to_gdb_tube,
-                #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-                guest_mem,
-                msr_handlers,
-            );
-
-            let exit_evt = scoped_exit_evt.into();
-            let final_event = match exit_reason {
-                ExitState::Stop => Some(exit_evt),
-                ExitState::Reset => Some(reset_evt),
-                ExitState::Crash => Some(crash_evt),
+            let final_event_data = match vcpu_fn() {
+                ExitState::Stop => VmEventType::Exit,
+                ExitState::Reset => VmEventType::Reset,
+                ExitState::Crash => VmEventType::Crash,
                 // vcpu_loop doesn't exit with GuestPanic.
-                ExitState::GuestPanic => None,
+                ExitState::GuestPanic => unreachable!(),
             };
-            if let Some(final_event) = final_event {
-                if let Err(e) = final_event.write(1) {
-                    error!(
-                        "failed to send final event {:?} on vcpu {}: {}",
-                        final_event, cpu_id, e
-                    )
-                }
+            if let Err(e) = vm_evt_wrtube.send::<VmEventType>(&final_event_data) {
+                error!(
+                    "failed to send final event {:?} on vcpu {}: {}",
+                    final_event_data, cpu_id, e
+                )
             }
         })
         .context("failed to spawn VCPU thread")
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 7ae7e7f..2fe9ad7 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -52,7 +52,6 @@
 use once_cell::sync::OnceCell;
 use std::arch::x86_64::__cpuid;
 use std::collections::BTreeMap;
-use std::convert::TryFrom;
 use std::ffi::{CStr, CString};
 use std::fs::File;
 use std::io::{self, Seek};
@@ -66,7 +65,7 @@
     get_serial_cmdline, GetSerialCmdlineError, MsrAction, MsrConfig, MsrRWType, MsrValueFrom,
     RunnableLinuxVm, VmComponents, VmImage,
 };
-use base::{warn, Event};
+use base::{warn, Event, SendTube, TubeError};
 use devices::serial_device::{SerialHardware, SerialParameters};
 use devices::{
     BusDeviceObj, BusResumeDevice, IrqChip, IrqChipX86_64, PciAddress, PciConfigIo, PciConfigMmio,
@@ -99,6 +98,8 @@
     CloneEvent(base::Error),
     #[error("failed to clone IRQ chip: {0}")]
     CloneIrqChip(base::Error),
+    #[error("unable to clone a Tube: {0}")]
+    CloneTube(TubeError),
     #[error("the given kernel command line was invalid: {0}")]
     Cmdline(kernel_cmdline::Error),
     #[error("failed to configure hotplugged pci device: {0}")]
@@ -498,8 +499,7 @@
 
     fn build_vm<V, Vcpu>(
         mut components: VmComponents,
-        exit_evt: &Event,
-        reset_evt: &Event,
+        vm_evt_wrtube: &SendTube,
         system_allocator: &mut SystemAllocator,
         serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
         serial_jail: Option<Minijail>,
@@ -576,7 +576,7 @@
         pci.lock().enable_pcie_cfg_mmio(read_pcie_cfg_mmio_start());
         let pci_cfg = PciConfigIo::new(
             pci.clone(),
-            reset_evt.try_clone().map_err(Error::CloneEvent)?,
+            vm_evt_wrtube.try_clone().map_err(Error::CloneTube)?,
         );
         let pci_bus = Arc::new(Mutex::new(pci_cfg));
         io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap();
@@ -606,7 +606,7 @@
             Self::setup_legacy_devices(
                 &io_bus,
                 irq_chip.pit_uses_speaker_port(),
-                reset_evt.try_clone().map_err(Error::CloneEvent)?,
+                vm_evt_wrtube.try_clone().map_err(Error::CloneTube)?,
                 components.memory_size,
             )?;
         }
@@ -627,7 +627,7 @@
             &io_bus,
             system_allocator,
             suspend_evt.try_clone().map_err(Error::CloneEvent)?,
-            exit_evt.try_clone().map_err(Error::CloneEvent)?,
+            vm_evt_wrtube.try_clone().map_err(Error::CloneTube)?,
             components.acpi_sdts,
             #[cfg(feature = "direct")]
             &components.direct_gpe,
@@ -1293,12 +1293,12 @@
     ///
     /// * - `io_bus` - the IO bus object
     /// * - `pit_uses_speaker_port` - does the PIT use port 0x61 for the PC speaker
-    /// * - `reset_evt` - the event object which should receive exit events
+    /// * - `vm_evt_wrtube` - Tube for sending exit events
     /// * - `mem_size` - the size in bytes of physical ram for the guest
     fn setup_legacy_devices(
         io_bus: &devices::Bus,
         pit_uses_speaker_port: bool,
-        reset_evt: Event,
+        vm_evt_wrtube: SendTube,
         mem_size: u64,
     ) -> Result<()> {
         let mem_regions = arch_memory_regions(mem_size, None);
@@ -1324,7 +1324,7 @@
             .unwrap();
 
         let i8042 = Arc::new(Mutex::new(devices::I8042Device::new(
-            reset_evt.try_clone().map_err(Error::CloneEvent)?,
+            vm_evt_wrtube.try_clone().map_err(Error::CloneTube)?,
         )));
 
         if pit_uses_speaker_port {
@@ -1354,7 +1354,7 @@
         io_bus: &devices::Bus,
         resources: &mut SystemAllocator,
         suspend_evt: Event,
-        exit_evt: Event,
+        vm_evt_wrtube: SendTube,
         sdts: Vec<SDT>,
         #[cfg(feature = "direct")] direct_gpe: &[u32],
         irq_chip: &mut dyn IrqChip,
@@ -1429,7 +1429,7 @@
             #[cfg(feature = "direct")]
             direct_gpe_info,
             suspend_evt,
-            exit_evt,
+            vm_evt_wrtube,
         );
         pmresource.to_aml_bytes(&mut amls);
         pmresource.start();
diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs
index 1a206cd..0e298d9 100644
--- a/x86_64/src/test_integration.rs
+++ b/x86_64/src/test_integration.rs
@@ -124,7 +124,7 @@
 
     let mmio_bus = Arc::new(devices::Bus::new());
     let io_bus = Arc::new(devices::Bus::new());
-    let exit_evt = Event::new().unwrap();
+    let (exit_evt_wrtube, _) = Tube::directional_pair().unwrap();
 
     let mut control_tubes = vec![TaggedControlTube::VmIrq(irqchip_tube)];
     // Create one control socket per disk.
@@ -153,13 +153,14 @@
     )
     .unwrap();
     let pci = Arc::new(Mutex::new(pci));
-    let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci, Event::new().unwrap())));
+    let (pcibus_exit_evt_wrtube, _) = Tube::directional_pair().unwrap();
+    let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci, pcibus_exit_evt_wrtube)));
     io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap();
 
     X8664arch::setup_legacy_devices(
         &io_bus,
         irq_chip.pit_uses_speaker_port(),
-        exit_evt.try_clone().unwrap(),
+        exit_evt_wrtube.try_clone().unwrap(),
         memory_size,
     )
     .unwrap();
@@ -204,7 +205,9 @@
         suspend_evt
             .try_clone()
             .expect("unable to clone suspend_evt"),
-        exit_evt.try_clone().expect("unable to clone exit_evt"),
+        exit_evt_wrtube
+            .try_clone()
+            .expect("unable to clone exit_evt_wrtube"),
         Default::default(),
         &mut irq_chip,
         X86_64_SCI_IRQ,