Merge "Set up timeout for graceful shutdown" into main
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 8934de0..f0eba7f 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -56,6 +56,7 @@
         "libvirtualizationservice_jni",
         "libvirtualmachine_jni",
     ],
+    native_shared_libs: ["libavf"],
     // TODO(b/295593640) Unfortunately these are added to the apex even though they are unused.
     // Once the build system is fixed, remove this.
     unwanted_transitive_deps: [
@@ -108,7 +109,6 @@
                 "rialto_bin",
                 "android_bootloader_crosvm_aarch64",
             ],
-            native_shared_libs: ["libavf"],
         },
         x86_64: {
             binaries: [
@@ -129,7 +129,6 @@
             prebuilts: [
                 "android_bootloader_crosvm_x86_64",
             ],
-            native_shared_libs: ["libavf"],
         },
     },
     binaries: [
diff --git a/guest/pvmfw/README.md b/guest/pvmfw/README.md
index c7f3dd6..08b0d5c 100644
--- a/guest/pvmfw/README.md
+++ b/guest/pvmfw/README.md
@@ -1,19 +1,30 @@
 # Protected Virtual Machine Firmware
 
+## Protected VM (_"pVM"_)
+
 In the context of the [Android Virtualization Framework][AVF], a hypervisor
 (_e.g._ [pKVM]) enforces full memory isolation between its virtual machines
-(VMs) and the host.  As a result, the host is only allowed to access memory that
-has been explicitly shared back by a VM. Such _protected VMs_ (“pVMs”) are
-therefore able to manipulate secrets without being at risk of an attacker
-stealing them by compromising the Android host.
+(VMs) and the host. As a result, such VMs are given strong guarantees regarding
+their confidentiality and integrity.
 
-As pVMs are started dynamically by a _virtual machine manager_ (“VMM”) running
-as a host process and as pVMs must not trust the host (see [_Why
-AVF?_][why-avf]), the virtual machine it configures can't be trusted either.
-Furthermore, even though the isolation mentioned above allows pVMs to protect
-their secrets from the host, it does not help with provisioning them during
-boot. In particular, the threat model would prohibit the host from ever having
-access to those secrets, preventing the VMM from passing them to the pVM.
+A _protected VMs_ (“pVMs”) is a VM running in the non-secure or realm world,
+started dynamically by a _virtual machine manager_ (“VMM”) running as a process
+of the untrusted Android host (see [_Why AVF?_][why-avf]) and which is isolated
+from the host OS so that access to its memory is restricted, even in the event
+of a compromised Android host. pVMs support rich environments, including
+Linux-based distributions.
+
+The pVM concept is not Google-exclusive. Partner-defined VMs (SoC/OEM) meeting
+isolation/memory access restrictions are also pVMs.
+
+## Protected VM Root-of-Trust: pvmfw
+
+As pVMs are managed by a VMM running on the untrusted host, the virtual machine
+it configures can't be trusted either. Furthermore, even though the isolation
+mentioned above allows pVMs to protect their secrets from the host, it does not
+help with provisioning them during boot. In particular, the threat model would
+prohibit the host from ever having access to those secrets, preventing the VMM
+from passing them to the pVM.
 
 To address these concerns the hypervisor securely loads the pVM firmware
 (“pvmfw”) in the pVM from a protected memory region (this prevents the host or
diff --git a/guest/pvmfw/src/arch/aarch64/exceptions.rs b/guest/pvmfw/src/arch/aarch64/exceptions.rs
index 4c867fb..c8c0156 100644
--- a/guest/pvmfw/src/arch/aarch64/exceptions.rs
+++ b/guest/pvmfw/src/arch/aarch64/exceptions.rs
@@ -18,9 +18,7 @@
     arch::aarch64::exceptions::{
         handle_permission_fault, handle_translation_fault, ArmException, Esr, HandleExceptionError,
     },
-    eprintln, logger,
-    power::reboot,
-    read_sysreg,
+    logger, read_sysreg,
 };
 
 fn handle_exception(exception: &ArmException) -> Result<(), HandleExceptionError> {
@@ -41,55 +39,44 @@
 
     let exception = ArmException::from_el1_regs();
     if let Err(e) = handle_exception(&exception) {
-        exception.print("sync_exception_current", e, elr);
-        reboot()
+        exception.print_and_reboot("sync_exception_current", e, elr);
     }
 }
 
 #[no_mangle]
 extern "C" fn irq_current(_elr: u64, _spsr: u64) {
-    eprintln!("irq_current");
-    reboot();
+    panic!("irq_current");
 }
 
 #[no_mangle]
 extern "C" fn fiq_current(_elr: u64, _spsr: u64) {
-    eprintln!("fiq_current");
-    reboot();
+    panic!("fiq_current");
 }
 
 #[no_mangle]
 extern "C" fn serr_current(_elr: u64, _spsr: u64) {
     let esr = read_sysreg!("esr_el1");
-    eprintln!("serr_current");
-    eprintln!("esr={esr:#08x}");
-    reboot();
+    panic!("serr_current, esr={esr:#08x}");
 }
 
 #[no_mangle]
 extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
     let esr = read_sysreg!("esr_el1");
-    eprintln!("sync_lower");
-    eprintln!("esr={esr:#08x}");
-    reboot();
+    panic!("sync_lower, esr={esr:#08x}");
 }
 
 #[no_mangle]
 extern "C" fn irq_lower(_elr: u64, _spsr: u64) {
-    eprintln!("irq_lower");
-    reboot();
+    panic!("irq_lower");
 }
 
 #[no_mangle]
 extern "C" fn fiq_lower(_elr: u64, _spsr: u64) {
-    eprintln!("fiq_lower");
-    reboot();
+    panic!("fiq_lower");
 }
 
 #[no_mangle]
 extern "C" fn serr_lower(_elr: u64, _spsr: u64) {
     let esr = read_sysreg!("esr_el1");
-    eprintln!("serr_lower");
-    eprintln!("esr={esr:#08x}");
-    reboot();
+    panic!("serr_lower, esr={esr:#08x}");
 }
diff --git a/guest/pvmfw/src/bcc.rs b/guest/pvmfw/src/bcc.rs
index 9260d7f..7ce50e9 100644
--- a/guest/pvmfw/src/bcc.rs
+++ b/guest/pvmfw/src/bcc.rs
@@ -97,8 +97,6 @@
 }
 
 impl Bcc {
-    /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
-    /// secure).
     pub fn new(received_bcc: Option<&[u8]>) -> Result<Bcc> {
         let received_bcc = received_bcc.unwrap_or(&[]);
         if received_bcc.is_empty() {
@@ -132,6 +130,8 @@
         Ok(Self { is_debug_mode, leaf_subject_pubkey })
     }
 
+    /// Returns whether any node in the received DICE chain is marked as debug (and hence is not
+    /// secure).
     pub fn is_debug_mode(&self) -> bool {
         self.is_debug_mode
     }
diff --git a/guest/pvmfw/src/entry.rs b/guest/pvmfw/src/entry.rs
index 8ada6a1..cde4cfe 100644
--- a/guest/pvmfw/src/entry.rs
+++ b/guest/pvmfw/src/entry.rs
@@ -98,7 +98,7 @@
     };
 
     const REBOOT_REASON_CONSOLE: usize = 1;
-    console_writeln!(REBOOT_REASON_CONSOLE, "{}", reboot_reason.as_avf_reboot_string());
+    console_writeln!(REBOOT_REASON_CONSOLE, "{}", reboot_reason.as_avf_reboot_string()).unwrap();
     reboot()
 
     // if we reach this point and return, vmbase::entry::rust_entry() will call power::shutdown().
diff --git a/guest/pvmfw/src/fdt.rs b/guest/pvmfw/src/fdt.rs
index 59399b3..6f55c21 100644
--- a/guest/pvmfw/src/fdt.rs
+++ b/guest/pvmfw/src/fdt.rs
@@ -1382,7 +1382,7 @@
         fdt.unpack()?;
     }
 
-    patch_dice_node(fdt, bcc.as_ptr() as usize, bcc.len())?;
+    patch_dice_node(fdt, bcc)?;
 
     if let Some(mut chosen) = fdt.chosen_mut()? {
         empty_or_delete_prop(&mut chosen, c"avf,strict-boot", strict_boot)?;
@@ -1401,16 +1401,14 @@
 }
 
 /// Patch the "google,open-dice"-compatible reserved-memory node to point to the bcc range
-fn patch_dice_node(fdt: &mut Fdt, addr: usize, size: usize) -> libfdt::Result<()> {
-    // We reject DTs with missing reserved-memory node as validation should have checked that the
-    // "swiotlb" subnode (compatible = "restricted-dma-pool") was present.
-    let node = fdt.node_mut(c"/reserved-memory")?.ok_or(libfdt::FdtError::NotFound)?;
-
+fn patch_dice_node(fdt: &mut Fdt, handover: &[u8]) -> libfdt::Result<()> {
+    // The node is assumed to be present in the template DT.
+    let node = fdt.node_mut(c"/reserved-memory")?.ok_or(FdtError::NotFound)?;
     let mut node = node.next_compatible(c"google,open-dice")?.ok_or(FdtError::NotFound)?;
 
-    let addr: u64 = addr.try_into().unwrap();
-    let size: u64 = size.try_into().unwrap();
-    node.setprop_inplace(c"reg", [addr.to_be_bytes(), size.to_be_bytes()].as_flattened())
+    let addr = (handover.as_ptr() as usize).try_into().unwrap();
+    let size = handover.len().try_into().unwrap();
+    node.setprop_addrrange_inplace(c"reg", addr, size)
 }
 
 fn empty_or_delete_prop(
diff --git a/guest/rialto/src/exceptions.rs b/guest/rialto/src/exceptions.rs
index 467a3a6..c8c0156 100644
--- a/guest/rialto/src/exceptions.rs
+++ b/guest/rialto/src/exceptions.rs
@@ -18,9 +18,7 @@
     arch::aarch64::exceptions::{
         handle_permission_fault, handle_translation_fault, ArmException, Esr, HandleExceptionError,
     },
-    eprintln, logger,
-    power::reboot,
-    read_sysreg,
+    logger, read_sysreg,
 };
 
 fn handle_exception(exception: &ArmException) -> Result<(), HandleExceptionError> {
@@ -41,58 +39,44 @@
 
     let exception = ArmException::from_el1_regs();
     if let Err(e) = handle_exception(&exception) {
-        exception.print("sync_exception_current", e, elr);
-        reboot()
+        exception.print_and_reboot("sync_exception_current", e, elr);
     }
 }
 
 #[no_mangle]
-extern "C" fn irq_current() {
-    eprintln!("irq_current");
-    reboot();
+extern "C" fn irq_current(_elr: u64, _spsr: u64) {
+    panic!("irq_current");
 }
 
 #[no_mangle]
-extern "C" fn fiq_current() {
-    eprintln!("fiq_current");
-    reboot();
+extern "C" fn fiq_current(_elr: u64, _spsr: u64) {
+    panic!("fiq_current");
 }
 
 #[no_mangle]
-extern "C" fn serr_current() {
-    eprintln!("serr_current");
-    print_esr();
-    reboot();
-}
-
-#[no_mangle]
-extern "C" fn sync_lower() {
-    eprintln!("sync_lower");
-    print_esr();
-    reboot();
-}
-
-#[no_mangle]
-extern "C" fn irq_lower() {
-    eprintln!("irq_lower");
-    reboot();
-}
-
-#[no_mangle]
-extern "C" fn fiq_lower() {
-    eprintln!("fiq_lower");
-    reboot();
-}
-
-#[no_mangle]
-extern "C" fn serr_lower() {
-    eprintln!("serr_lower");
-    print_esr();
-    reboot();
-}
-
-#[inline]
-fn print_esr() {
+extern "C" fn serr_current(_elr: u64, _spsr: u64) {
     let esr = read_sysreg!("esr_el1");
-    eprintln!("esr={:#08x}", esr);
+    panic!("serr_current, esr={esr:#08x}");
+}
+
+#[no_mangle]
+extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
+    let esr = read_sysreg!("esr_el1");
+    panic!("sync_lower, esr={esr:#08x}");
+}
+
+#[no_mangle]
+extern "C" fn irq_lower(_elr: u64, _spsr: u64) {
+    panic!("irq_lower");
+}
+
+#[no_mangle]
+extern "C" fn fiq_lower(_elr: u64, _spsr: u64) {
+    panic!("fiq_lower");
+}
+
+#[no_mangle]
+extern "C" fn serr_lower(_elr: u64, _spsr: u64) {
+    let esr = read_sysreg!("esr_el1");
+    panic!("serr_lower, esr={esr:#08x}");
 }
diff --git a/guest/trusty/security_vm/security_vm.mk b/guest/trusty/security_vm/security_vm.mk
index 89c9cdc..8717399 100644
--- a/guest/trusty/security_vm/security_vm.mk
+++ b/guest/trusty/security_vm/security_vm.mk
@@ -14,10 +14,14 @@
 
 
 ifeq ($(findstring enabled, $(TRUSTY_SYSTEM_VM)),enabled)
-PRODUCT_PACKAGES += \
-	trusty_security_vm.elf \
+
+# This is the default set of packages to load the trusty system vm.
+# It can be overridden by device-specific configuration.
+TRUSTY_SYSTEM_VM_PRODUCT_PACKAGES ?= trusty_security_vm.elf \
 	trusty_security_vm_launcher \
 	trusty_security_vm_launcher.rc \
 	early_vms.xml \
 
+PRODUCT_PACKAGES += $(TRUSTY_SYSTEM_VM_PRODUCT_PACKAGES)
+
 endif
diff --git a/guest/vmbase_example/src/exceptions.rs b/guest/vmbase_example/src/exceptions.rs
index 5d7768a..0eb415c 100644
--- a/guest/vmbase_example/src/exceptions.rs
+++ b/guest/vmbase_example/src/exceptions.rs
@@ -14,62 +14,51 @@
 
 //! Exception handlers.
 
-use vmbase::{eprintln, power::reboot, read_sysreg};
+use vmbase::{arch::aarch64::exceptions::ArmException, read_sysreg};
 
 #[no_mangle]
-extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
-    eprintln!("sync_exception_current");
-    print_esr();
-    reboot();
+extern "C" fn sync_exception_current(elr: u64, _spsr: u64) {
+    ArmException::from_el1_regs().print_and_reboot(
+        "sync_exception_current",
+        "Unexpected synchronous exception",
+        elr,
+    );
 }
 
 #[no_mangle]
 extern "C" fn irq_current(_elr: u64, _spsr: u64) {
-    eprintln!("irq_current");
-    reboot();
+    panic!("irq_current");
 }
 
 #[no_mangle]
 extern "C" fn fiq_current(_elr: u64, _spsr: u64) {
-    eprintln!("fiq_current");
-    reboot();
+    panic!("fiq_current");
 }
 
 #[no_mangle]
 extern "C" fn serr_current(_elr: u64, _spsr: u64) {
-    eprintln!("serr_current");
-    print_esr();
-    reboot();
+    let esr = read_sysreg!("esr_el1");
+    panic!("serr_current, esr={:#08x}", esr);
 }
 
 #[no_mangle]
 extern "C" fn sync_lower(_elr: u64, _spsr: u64) {
-    eprintln!("sync_lower");
-    print_esr();
-    reboot();
+    let esr = read_sysreg!("esr_el1");
+    panic!("sync_lower, esr={:#08x}", esr);
 }
 
 #[no_mangle]
 extern "C" fn irq_lower(_elr: u64, _spsr: u64) {
-    eprintln!("irq_lower");
-    reboot();
+    panic!("irq_lower");
 }
 
 #[no_mangle]
 extern "C" fn fiq_lower(_elr: u64, _spsr: u64) {
-    eprintln!("fiq_lower");
-    reboot();
+    panic!("fiq_lower");
 }
 
 #[no_mangle]
 extern "C" fn serr_lower(_elr: u64, _spsr: u64) {
-    eprintln!("serr_lower");
-    print_esr();
-    reboot();
-}
-
-#[inline]
-fn print_esr() {
     let esr = read_sysreg!("esr_el1");
-    eprintln!("esr={:#08x}", esr);
+    panic!("serr_lower, esr={:#08x}", esr);
 }
diff --git a/libs/libavf/Android.bp b/libs/libavf/Android.bp
index aceb927..4469af5 100644
--- a/libs/libavf/Android.bp
+++ b/libs/libavf/Android.bp
@@ -40,30 +40,8 @@
     defaults: ["libavf.default"],
 }
 
-soong_config_module_type {
-    name: "virt_cc_defaults",
-    module_type: "cc_defaults",
-    config_namespace: "ANDROID",
-    bool_variables: [
-        "avf_enabled",
-    ],
-    properties: [
-        "apex_available",
-    ],
-}
-
-virt_cc_defaults {
-    name: "libavf_apex_available_defaults",
-    soong_config_variables: {
-        avf_enabled: {
-            apex_available: ["com.android.virt"],
-        },
-    },
-}
-
 cc_library {
     name: "libavf",
-    defaults: ["libavf_apex_available_defaults"],
     llndk: {
         symbol_file: "libavf.map.txt",
         moved_to_apex: true,
@@ -79,4 +57,5 @@
     stubs: {
         symbol_file: "libavf.map.txt",
     },
+    apex_available: ["com.android.virt"],
 }
diff --git a/libs/libvmbase/README.md b/libs/libvmbase/README.md
index 28d930a..3c05cc3 100644
--- a/libs/libvmbase/README.md
+++ b/libs/libvmbase/README.md
@@ -79,23 +79,16 @@
 use vmbase::power::reboot;
 
 extern "C" fn sync_exception_current() {
-    eprintln!("sync_exception_current");
-
     let mut esr: u64;
     unsafe {
         asm!("mrs {esr}, esr_el1", esr = out(reg) esr);
     }
-    eprintln!("esr={:#08x}", esr);
-
-    reboot();
+    panic!("sync_exception_current, esr={:#08x}", esr);
 }
 ```
 
 The `println!` macro shouldn't be used in exception handlers, because it relies on a global instance
 of the UART driver which might be locked when the exception happens, which would result in deadlock.
-Instead you can use `eprintln!`, which will re-initialize the UART every time to ensure that it can
-be used. This should still be used with care, as it may interfere with whatever the rest of the
-program is doing with the UART.
 
 See [example/src/exceptions.rs](examples/src/exceptions.rs) for a complete example.
 
diff --git a/libs/libvmbase/src/arch/aarch64/exceptions.rs b/libs/libvmbase/src/arch/aarch64/exceptions.rs
index 1868bf7..787ef37 100644
--- a/libs/libvmbase/src/arch/aarch64/exceptions.rs
+++ b/libs/libvmbase/src/arch/aarch64/exceptions.rs
@@ -14,12 +14,17 @@
 
 //! Helper functions and structs for exception handlers.
 
-use crate::memory::{MemoryTrackerError, MEMORY};
 use crate::{
-    arch::aarch64::layout::UART_PAGE_ADDR, arch::VirtualAddress, eprintln, memory::page_4kb_of,
+    arch::{
+        aarch64::layout::UART_PAGE_ADDR,
+        platform::{emergency_uart, DEFAULT_EMERGENCY_CONSOLE_INDEX},
+        VirtualAddress,
+    },
+    memory::{page_4kb_of, MemoryTrackerError, MEMORY},
+    power::reboot,
     read_sysreg,
 };
-use core::fmt;
+use core::fmt::{self, Write};
 use core::result;
 
 /// Represents an error that can occur while handling an exception.
@@ -122,14 +127,23 @@
         Self { esr, far: VirtualAddress(far) }
     }
 
-    /// Prints the details of an obj and the exception, excluding UART exceptions.
-    pub fn print<T: fmt::Display>(&self, exception_name: &str, obj: T, elr: u64) {
+    /// Prints the details of an obj and the exception, excluding UART exceptions, and then reboots.
+    ///
+    /// This uses the emergency console so can safely be called even for synchronous exceptions
+    /// without causing a deadlock.
+    pub fn print_and_reboot<T: fmt::Display>(&self, exception_name: &str, obj: T, elr: u64) -> ! {
         // Don't print to the UART if we are handling an exception it could raise.
         if !self.is_uart_exception() {
-            eprintln!("{exception_name}");
-            eprintln!("{obj}");
-            eprintln!("{}, elr={:#08x}", self, elr);
+            // SAFETY: We always reboot at the end of this method so there is no way for the
+            // original UART driver to be used after this.
+            if let Some(mut console) = unsafe { emergency_uart(DEFAULT_EMERGENCY_CONSOLE_INDEX) } {
+                // Ignore errors writing to emergency console, as we are about to reboot anyway.
+                _ = writeln!(console, "{exception_name}");
+                _ = writeln!(console, "{obj}");
+                _ = writeln!(console, "{}, elr={:#08x}", self, elr);
+            }
         }
+        reboot();
     }
 
     fn is_uart_exception(&self) -> bool {
diff --git a/libs/libvmbase/src/arch/aarch64/platform.rs b/libs/libvmbase/src/arch/aarch64/platform.rs
index b33df68..7e002d0 100644
--- a/libs/libvmbase/src/arch/aarch64/platform.rs
+++ b/libs/libvmbase/src/arch/aarch64/platform.rs
@@ -104,18 +104,25 @@
 
 /// Return platform uart with specific index
 ///
-/// Panics if console was not initialized by calling [`init`] first.
-pub fn uart(id: usize) -> &'static spin::mutex::SpinMutex<Uart> {
-    CONSOLES[id].get().unwrap()
+/// Returns `None` if console was not initialized by calling [`init`] first.
+pub fn uart(id: usize) -> Option<&'static SpinMutex<Uart>> {
+    CONSOLES[id].get()
 }
 
-/// Reinitializes the emergency UART driver and returns it.
+/// Reinitializes the n-th UART driver and returns it.
 ///
 /// This is intended for use in situations where the UART may be in an unknown state or the global
-/// instance may be locked, such as in an exception handler or panic handler.
-pub fn emergency_uart() -> Uart {
+/// instance may be locked, such as in the synchronous exception handler.
+///
+/// # Safety
+///
+/// This takes over the UART from wherever it is being used, the existing UART instance should not
+/// be used after this is called. This should only be used immediately before aborting the VM.
+pub unsafe fn emergency_uart(id: usize) -> Option<Uart> {
+    let addr = *ADDRESSES[id].get()?;
+
     // SAFETY: Initialization of UART using dedicated const address.
-    unsafe { Uart::new(UART_ADDRESSES[DEFAULT_EMERGENCY_CONSOLE_INDEX]) }
+    Some(unsafe { Uart::new(addr) })
 }
 
 /// Makes a `PSCI_SYSTEM_OFF` call to shutdown the VM.
diff --git a/libs/libvmbase/src/console.rs b/libs/libvmbase/src/console.rs
index 6d9a4fe..dcaf1ad 100644
--- a/libs/libvmbase/src/console.rs
+++ b/libs/libvmbase/src/console.rs
@@ -14,33 +14,26 @@
 
 //! Console driver for 8250 UART.
 
-use crate::arch::platform;
-use core::fmt::{write, Arguments, Write};
+use crate::arch::platform::{self, emergency_uart, DEFAULT_EMERGENCY_CONSOLE_INDEX};
+use crate::power::reboot;
+use core::fmt::{self, write, Arguments, Write};
+use core::panic::PanicInfo;
 
 /// Writes a formatted string followed by a newline to the n-th console.
 ///
-/// Panics if the n-th console was not initialized by calling [`init`] first.
-pub fn writeln(n: usize, format_args: Arguments) {
-    let uart = &mut *platform::uart(n).lock();
-    write(uart, format_args).unwrap();
-    let _ = uart.write_str("\n");
-}
+/// Returns an error if the n-th console was not initialized by calling [`init`] first.
+pub fn writeln(n: usize, format_args: Arguments) -> fmt::Result {
+    let uart = &mut *platform::uart(n).ok_or(fmt::Error)?.lock();
 
-/// Reinitializes the emergency UART driver and writes a formatted string followed by a newline to
-/// it.
-///
-/// This is intended for use in situations where the UART may be in an unknown state or the global
-/// instance may be locked, such as in an exception handler or panic handler.
-pub fn ewriteln(format_args: Arguments) {
-    let mut uart = platform::emergency_uart();
-    let _ = write(&mut uart, format_args);
-    let _ = uart.write_str("\n");
+    write(uart, format_args)?;
+    uart.write_str("\n")?;
+    Ok(())
 }
 
 /// Prints the given formatted string to the n-th console, followed by a newline.
 ///
-/// Panics if the console has not yet been initialized. May hang if used in an exception context;
-/// use `eprintln!` instead.
+/// Returns an error if the console has not yet been initialized. May deadlock if used in a
+/// synchronous exception handler.
 #[macro_export]
 macro_rules! console_writeln {
     ($n:expr, $($arg:tt)*) => ({
@@ -48,27 +41,24 @@
     })
 }
 
-pub(crate) use console_writeln;
-
 /// Prints the given formatted string to the console, followed by a newline.
 ///
-/// Panics if the console has not yet been initialized. May hang if used in an exception context;
-/// use `eprintln!` instead.
+/// Panics if the console has not yet been initialized. May hang if used in an exception context.
 macro_rules! println {
     ($($arg:tt)*) => ({
-        $crate::console::console_writeln!($crate::arch::platform::DEFAULT_CONSOLE_INDEX, $($arg)*)
+        $crate::console_writeln!($crate::arch::platform::DEFAULT_CONSOLE_INDEX, $($arg)*).unwrap()
     })
 }
 
 pub(crate) use println; // Make it available in this crate.
 
-/// Prints the given string followed by a newline to the console in an emergency, such as an
-/// exception handler.
-///
-/// Never panics.
-#[macro_export]
-macro_rules! eprintln {
-    ($($arg:tt)*) => ({
-        $crate::console::ewriteln(format_args!($($arg)*))
-    })
+#[panic_handler]
+fn panic(info: &PanicInfo) -> ! {
+    // SAFETY: We always reboot at the end of this method so there is no way for the
+    // original UART driver to be used after this.
+    if let Some(mut console) = unsafe { emergency_uart(DEFAULT_EMERGENCY_CONSOLE_INDEX) } {
+        // Ignore errors, to avoid a panic loop.
+        let _ = writeln!(console, "{}", info);
+    }
+    reboot()
 }
diff --git a/libs/libvmbase/src/lib.rs b/libs/libvmbase/src/lib.rs
index d254038..afb62fc 100644
--- a/libs/libvmbase/src/lib.rs
+++ b/libs/libvmbase/src/lib.rs
@@ -32,12 +32,3 @@
 pub mod uart;
 pub mod util;
 pub mod virtio;
-
-use core::panic::PanicInfo;
-use power::reboot;
-
-#[panic_handler]
-fn panic(info: &PanicInfo) -> ! {
-    eprintln!("{}", info);
-    reboot()
-}
diff --git a/libs/libvmbase/src/logger.rs b/libs/libvmbase/src/logger.rs
index 9130918..0059d11 100644
--- a/libs/libvmbase/src/logger.rs
+++ b/libs/libvmbase/src/logger.rs
@@ -16,7 +16,7 @@
 //!
 //! Internally uses the println! vmbase macro, which prints to crosvm's UART.
 //! Note: may not work if the VM is in an inconsistent state. Exception handlers
-//! should avoid using this logger and instead print with eprintln!.
+//! should avoid using this logger.
 
 use crate::console::println;
 use core::sync::atomic::{AtomicBool, Ordering};