vmbase: Improve safety of console::init()
As the console should only be accessed once it has been MMIO guarded and
mapped in the MMU, prevent eprintln!() from writing to the UART before
console::init() has been called.
Mark console::init() as unsafe, documenting the constraints on when it
should be called and, in the entry code, move it _after_ we've called
mmio_guard.map(UART_PAGE).
Move BASE_ADDRESS to the more appropriate layout module and allow the
caller to pass the UART base to console::init() and rename it UART_ADDR,
for clarity.
Test: m pvmfw librialto libvmbase_example
Change-Id: I9543765fd0429b38b45bc2e7b7c83bedf79194f3
diff --git a/vmbase/src/console.rs b/vmbase/src/console.rs
index 657a62b..7356d7f 100644
--- a/vmbase/src/console.rs
+++ b/vmbase/src/console.rs
@@ -15,26 +15,33 @@
//! Console driver for 8250 UART.
use crate::uart::Uart;
-use core::fmt::{write, Arguments, Write};
+use core::{
+ cell::OnceCell,
+ fmt::{write, Arguments, Write},
+};
use spin::mutex::SpinMutex;
-/// Base memory-mapped address of the primary UART device.
-pub const BASE_ADDRESS: usize = 0x3f8;
-
+// ADDRESS is the base of the MMIO region for a UART and must be mapped as device memory.
+static ADDRESS: SpinMutex<OnceCell<usize>> = SpinMutex::new(OnceCell::new());
static CONSOLE: SpinMutex<Option<Uart>> = SpinMutex::new(None);
-/// Initialises a new instance of the UART driver and returns it.
-fn create() -> Uart {
- // SAFETY: BASE_ADDRESS is the base of the MMIO region for a UART and is mapped as device
- // memory.
- unsafe { Uart::new(BASE_ADDRESS) }
-}
+/// Initialises the global instance of the UART driver.
+///
+/// This must be called before using the `print!` and `println!` macros.
+///
+/// # Safety
+///
+/// This must be called with the base of a UART, mapped as device memory and (if necessary) shared
+/// with the host as MMIO.
+pub unsafe fn init(base_address: usize) {
+ // Remember the valid address, for emergency console accesses.
+ ADDRESS.lock().set(base_address).expect("console::init() called more than once");
-/// Initialises the global instance of the UART driver. This must be called before using
-/// the `print!` and `println!` macros.
-pub fn init() {
- let uart = create();
- CONSOLE.lock().replace(uart);
+ // Initialize the console driver, for normal console accesses.
+ let mut console = CONSOLE.lock();
+ assert!(console.is_none(), "console::init() called more than once");
+ // SAFETY: base_address must be the base of a mapped UART.
+ console.replace(unsafe { Uart::new(base_address) });
}
/// Writes a formatted string followed by a newline to the console.
@@ -53,7 +60,12 @@
/// 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 = create();
+ let Some(cell) = ADDRESS.try_lock() else { return };
+ let Some(addr) = cell.get() else { return };
+
+ // SAFETY: addr contains the base of a mapped UART, passed in init().
+ let mut uart = unsafe { Uart::new(*addr) };
+
let _ = write(&mut uart, format_args);
let _ = uart.write_str("\n");
}