Clean up emergency console and use panic for unexpected exceptions.
This removes `eprintln`, which was easy to misuse and not really sound.
Instead, use `emergency_uart`, which has been made unsafe with a clearly
documented safety requirement that it can only be called when the normal
UART instance will never be used again, e.g. just before rebooting.
Also changed `emergency_uart` to return an error rather than panicking,
so panic handler can ignore the error.
Test: atest vmbase_example.integration_test
Change-Id: I76ac1911cf905fde0010054cfd8bc239699298f6
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/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/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/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/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};