Handle errors and crashes in VM differently to clean shutdown.

BUG=b:211704107
TEST=tools/dev_container tools/run_tests
TEST=tools/dev_container tools/run_tests --target=vm:aarch64

Change-Id: I383be89becefe300d9dc94b7d67d35269c628a39
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3412772
Reviewed-by: Dmitry Torokhov <dtor@chromium.org>
Auto-Submit: Andrew Walbran <qwandor@google.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Andrew Walbran <qwandor@google.com>
diff --git a/src/linux.rs b/src/linux.rs
index 3c4b00b..8b2583a 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -2477,6 +2477,7 @@
     mut mmio_bus: devices::Bus,
     exit_evt: Event,
     reset_evt: Event,
+    crash_evt: Event,
     requires_pvclock_ctrl: bool,
     from_main_tube: mpsc::Receiver<VcpuControl>,
     use_hypervisor_signals: bool,
@@ -2561,6 +2562,7 @@
             let final_event = match exit_reason {
                 ExitState::Stop => exit_evt,
                 ExitState::Reset => reset_evt,
+                ExitState::Crash => crash_evt,
             };
             if let Err(e) = final_event.write(1) {
                 error!(
@@ -2615,13 +2617,13 @@
                             Ok(m) => m,
                             Err(mpsc::RecvError) => {
                                 error!("Failed to read from main tube in vcpu");
-                                return ExitState::Stop;
+                                return ExitState::Crash;
                             }
                         }
                     }
                     Err(mpsc::TryRecvError::Disconnected) => {
                         error!("Failed to read from main tube in vcpu");
-                        return ExitState::Stop;
+                        return ExitState::Crash;
                     }
                 };
 
@@ -2762,7 +2764,7 @@
                     hardware_entry_failure_reason,
                 }) => {
                     error!("vcpu hw run failure: {:#x}", hardware_entry_failure_reason);
-                    return ExitState::Stop;
+                    return ExitState::Crash;
                 }
                 Ok(VcpuExit::SystemEventShutdown) => {
                     info!("system shutdown event on vcpu {}", cpu_id);
@@ -2786,7 +2788,7 @@
                         if let Some(ref ch) = to_gdb_tube {
                             if let Err(e) = ch.send(msg) {
                                 error!("failed to notify breakpoint to GDB thread: {}", e);
-                                return ExitState::Stop;
+                                return ExitState::Crash;
                             }
                         }
                         run_mode = VmRunMode::Breakpoint;
@@ -2798,7 +2800,7 @@
                     libc::EAGAIN => {}
                     _ => {
                         error!("vcpu hit unknown error: {}", e);
-                        return ExitState::Stop;
+                        return ExitState::Crash;
                     }
                 },
             }
@@ -2810,7 +2812,7 @@
                 // attempting to handle pause requests.
                 if let Err(e) = clear_signal(SIGRTMIN() + 0) {
                     error!("failed to clear pending signal: {}", e);
-                    return ExitState::Stop;
+                    return ExitState::Crash;
                 }
             } else {
                 vcpu.set_immediate_exit(false);
@@ -2915,6 +2917,7 @@
 pub enum ExitState {
     Reset,
     Stop,
+    Crash,
 }
 
 pub fn run_config(cfg: Config) -> Result<ExitState> {
@@ -3135,6 +3138,7 @@
 
     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 mut sys_allocator = Arch::create_system_allocator(&vm);
 
     // Allocate the ramoops region first. AArch64::build_vm() assumes this.
@@ -3329,6 +3333,7 @@
         usb_control_tube,
         exit_evt,
         reset_evt,
+        crash_evt,
         sigchld_fd,
         Arc::clone(&map_request),
         gralloc,
@@ -3473,6 +3478,7 @@
     #[cfg(feature = "usb")] usb_control_tube: Tube,
     exit_evt: Event,
     reset_evt: Event,
+    crash_evt: Event,
     sigchld_fd: SignalFd,
     map_request: Arc<Mutex<Option<ExternalMapping>>>,
     mut gralloc: RutabagaGralloc,
@@ -3482,6 +3488,7 @@
     enum Token {
         Exit,
         Reset,
+        Crash,
         Suspend,
         ChildSignal,
         IrqFd { index: IrqEventIndex },
@@ -3496,6 +3503,7 @@
     let wait_ctx = WaitContext::build_with(&[
         (&exit_evt, Token::Exit),
         (&reset_evt, Token::Reset),
+        (&crash_evt, Token::Crash),
         (&linux.suspend_evt, Token::Suspend),
         (&sigchld_fd, Token::ChildSignal),
     ])
@@ -3594,6 +3602,7 @@
             (*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")?,
             linux.vm.check_capability(VmCap::PvClockSuspend),
             from_main_channel,
             use_hypervisor_signals,
@@ -3662,6 +3671,11 @@
                     exit_state = ExitState::Reset;
                     break 'wait;
                 }
+                Token::Crash => {
+                    info!("vcpu crashed");
+                    exit_state = ExitState::Crash;
+                    break 'wait;
+                }
                 Token::Suspend => {
                     info!("VM requested suspend");
                     linux.suspend_evt.read().unwrap();
diff --git a/src/main.rs b/src/main.rs
index ef1b93b..3040dd6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2450,6 +2450,7 @@
     Success,
     VmReset,
     VmStop,
+    VmCrash,
 }
 
 fn run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()> {
@@ -2740,6 +2741,10 @@
                 info!("crosvm has exited normally due to reset request");
                 Ok(CommandStatus::VmReset)
             }
+            Ok(platform::ExitState::Crash) => {
+                info!("crosvm has exited due to a VM crash");
+                Ok(CommandStatus::VmCrash)
+            }
             Err(e) => {
                 error!("crosvm has exited with error: {:#}", e);
                 Err(())
@@ -3395,6 +3400,7 @@
     let exit_code = match crosvm_main() {
         Ok(CommandStatus::Success | CommandStatus::VmStop) => 0,
         Ok(CommandStatus::VmReset) => 32,
+        Ok(CommandStatus::VmCrash) => 33,
         Err(_) => 1,
     };
     std::process::exit(exit_code);