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/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]