vmbase: Support secondary UARTs 0x2f8,0x3e8,0x2e8

Parameterize the code to allow printing to the "n-th" console and
setting up an arbitrary (up to MAX_CONSOLES) number of UARTs to be used
by client code.

Extend the layout to describe the 4 UARTs crosvm unconditionally
generates and configure console::init() to allow printing to any one of
them.

Bug: 300636104
Test: m pvmfw librialto libvmbase_example
Change-Id: I899f70dfb5eeb41e4ba399aee59d731db1264ff9
diff --git a/vmbase/src/console.rs b/vmbase/src/console.rs
index 7356d7f..f829eb5 100644
--- a/vmbase/src/console.rs
+++ b/vmbase/src/console.rs
@@ -21,46 +21,64 @@
 };
 use spin::mutex::SpinMutex;
 
-// 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);
+// Arbitrary limit on the number of consoles that can be registered.
+//
+// Matches the UART count in crosvm.
+const MAX_CONSOLES: usize = 4;
 
-/// Initialises the global instance of the UART driver.
+static CONSOLES: [SpinMutex<Option<Uart>>; MAX_CONSOLES] =
+    [SpinMutex::new(None), SpinMutex::new(None), SpinMutex::new(None), SpinMutex::new(None)];
+static ADDRESSES: [SpinMutex<OnceCell<usize>>; MAX_CONSOLES] = [
+    SpinMutex::new(OnceCell::new()),
+    SpinMutex::new(OnceCell::new()),
+    SpinMutex::new(OnceCell::new()),
+    SpinMutex::new(OnceCell::new()),
+];
+
+/// Index of the console used by default for logging.
+pub const DEFAULT_CONSOLE_INDEX: usize = 0;
+
+/// Index of the console used by default for emergency logging.
+pub const DEFAULT_EMERGENCY_CONSOLE_INDEX: usize = DEFAULT_CONSOLE_INDEX;
+
+/// Initialises the global instance(s) 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");
+/// This must be called once with the bases of UARTs, mapped as device memory and (if necessary)
+/// shared with the host as MMIO, to which no other references must be held.
+pub unsafe fn init(base_addresses: &[usize]) {
+    for (i, &base_address) in base_addresses.iter().enumerate() {
+        // Remember the valid address, for emergency console accesses.
+        ADDRESSES[i].lock().set(base_address).expect("console::init() called more than once");
 
-    // 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) });
+        // Initialize the console driver, for normal console accesses.
+        let mut console = CONSOLES[i].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.
+/// Writes a formatted string followed by a newline to the n-th console.
 ///
-/// Panics if [`init`] was not called first.
-pub(crate) fn writeln(format_args: Arguments) {
-    let mut guard = CONSOLE.lock();
+/// Panics if the n-th console was not initialized by calling [`init`] first.
+pub(crate) fn writeln(n: usize, format_args: Arguments) {
+    let mut guard = CONSOLES[n].lock();
     let uart = guard.as_mut().unwrap();
 
     write(uart, format_args).unwrap();
     let _ = uart.write_str("\n");
 }
 
-/// Reinitializes the UART driver and writes a formatted string followed by a newline to it.
+/// Reinitializes the n-th 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 Some(cell) = ADDRESS.try_lock() else { return };
+pub fn ewriteln(n: usize, format_args: Arguments) {
+    let Some(cell) = ADDRESSES[n].try_lock() else { return };
     let Some(addr) = cell.get() else { return };
 
     // SAFETY: addr contains the base of a mapped UART, passed in init().
@@ -75,7 +93,9 @@
 /// Panics if the console has not yet been initialized. May hang if used in an exception context;
 /// use `eprintln!` instead.
 macro_rules! println {
-    ($($arg:tt)*) => ($crate::console::writeln(format_args!($($arg)*)));
+    ($($arg:tt)*) => ({
+        $crate::console::writeln($crate::console::DEFAULT_CONSOLE_INDEX, format_args!($($arg)*))
+    })
 }
 
 pub(crate) use println; // Make it available in this crate.
@@ -86,5 +106,7 @@
 /// Never panics.
 #[macro_export]
 macro_rules! eprintln {
-    ($($arg:tt)*) => ($crate::console::ewriteln(format_args!($($arg)*)));
+    ($($arg:tt)*) => ({
+        $crate::console::ewriteln($crate::console::DEFAULT_EMERGENCY_CONSOLE_INDEX, format_args!($($arg)*))
+    })
 }
diff --git a/vmbase/src/entry.rs b/vmbase/src/entry.rs
index 4a436d2..ad633ed 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -16,7 +16,7 @@
 
 use crate::{
     bionic, console, heap, hyp,
-    layout::{UART_ADDR, UART_PAGE_ADDR},
+    layout::{UART_ADDRESSES, UART_PAGE_ADDR},
     logger,
     memory::{SIZE_16KB, SIZE_4KB},
     power::{reboot, shutdown},
@@ -48,7 +48,7 @@
     }
 
     // SAFETY: UART_PAGE is mapped at stage-1 (see entry.S) and was just MMIO-guarded.
-    unsafe { console::init(UART_ADDR) };
+    unsafe { console::init(&UART_ADDRESSES) };
 
     Ok(())
 }
diff --git a/vmbase/src/layout.rs b/vmbase/src/layout.rs
index e65abda..5ac435f 100644
--- a/vmbase/src/layout.rs
+++ b/vmbase/src/layout.rs
@@ -26,14 +26,17 @@
 /// First address that can't be translated by a level 1 TTBR0_EL1.
 pub const MAX_VIRT_ADDR: usize = 1 << 40;
 
-/// Base memory-mapped address of the primary UART device.
+/// Base memory-mapped addresses of the UART devices.
 ///
 /// See SERIAL_ADDR in https://crosvm.dev/book/appendix/memory_layout.html#common-layout.
-pub const UART_ADDR: usize = 0x3f8;
+pub const UART_ADDRESSES: [usize; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
 
 /// Address of the single page containing all the UART devices.
 pub const UART_PAGE_ADDR: usize = 0;
-const_assert_eq!(UART_PAGE_ADDR, page_4kb_of(UART_ADDR));
+const_assert_eq!(UART_PAGE_ADDR, page_4kb_of(UART_ADDRESSES[0]));
+const_assert_eq!(UART_PAGE_ADDR, page_4kb_of(UART_ADDRESSES[1]));
+const_assert_eq!(UART_PAGE_ADDR, page_4kb_of(UART_ADDRESSES[2]));
+const_assert_eq!(UART_PAGE_ADDR, page_4kb_of(UART_ADDRESSES[3]));
 
 /// Get an address from a linker-defined symbol.
 #[macro_export]