Merge changes I313c5c19,Ia09dd908 into main

* changes:
  Add docs for Microdroid vendor modules
  pvmfw/README.md: add subheaders for different versions of pvmfw data
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index cc6e302..0ff7270 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -30,7 +30,7 @@
 use log::LevelFilter;
 use vmbase::util::RangeExt as _;
 use vmbase::{
-    configure_heap,
+    configure_heap, console_writeln,
     hyp::{get_mem_sharer, get_mmio_guard},
     layout::{self, crosvm, UART_PAGE_ADDR},
     main,
@@ -59,6 +59,21 @@
     SecretDerivationError,
 }
 
+impl RebootReason {
+    pub fn as_avf_reboot_string(&self) -> &'static str {
+        match self {
+            Self::InvalidBcc => "PVM_FIRMWARE_INVALID_BCC",
+            Self::InvalidConfig => "PVM_FIRMWARE_INVALID_CONFIG_DATA",
+            Self::InternalError => "PVM_FIRMWARE_INTERNAL_ERROR",
+            Self::InvalidFdt => "PVM_FIRMWARE_INVALID_FDT",
+            Self::InvalidPayload => "PVM_FIRMWARE_INVALID_PAYLOAD",
+            Self::InvalidRamdisk => "PVM_FIRMWARE_INVALID_RAMDISK",
+            Self::PayloadVerificationError => "PVM_FIRMWARE_PAYLOAD_VERIFICATION_FAILED",
+            Self::SecretDerivationError => "PVM_FIRMWARE_SECRET_DERIVATION_FAILED",
+        }
+    }
+}
+
 main!(start);
 configure_heap!(SIZE_128KB);
 
@@ -66,11 +81,15 @@
 pub fn start(fdt_address: u64, payload_start: u64, payload_size: u64, _arg3: u64) {
     // Limitations in this function:
     // - can't access non-pvmfw memory (only statically-mapped memory)
-    // - can't access MMIO (therefore, no logging)
+    // - can't access MMIO (except the console, already configured by vmbase)
 
     match main_wrapper(fdt_address as usize, payload_start as usize, payload_size as usize) {
         Ok((entry, bcc)) => jump_to_payload(fdt_address, entry.try_into().unwrap(), bcc),
-        Err(_) => reboot(), // TODO(b/220071963) propagate the reason back to the host.
+        Err(e) => {
+            const REBOOT_REASON_CONSOLE: usize = 1;
+            console_writeln!(REBOOT_REASON_CONSOLE, "{}", e.as_avf_reboot_string());
+            reboot()
+        }
     }
 
     // if we reach this point and return, vmbase::entry::rust_entry() will call power::shutdown().
diff --git a/vmbase/src/console.rs b/vmbase/src/console.rs
index a7d37b4..bbbcb07 100644
--- a/vmbase/src/console.rs
+++ b/vmbase/src/console.rs
@@ -15,91 +15,111 @@
 //! 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;
+// Arbitrary limit on the number of consoles that can be registered.
+//
+// Matches the UART count in crosvm.
+const MAX_CONSOLES: usize = 4;
 
-static CONSOLE: SpinMutex<Option<Uart>> = SpinMutex::new(None);
+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()),
+];
 
-/// 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) }
-}
+/// Index of the console used by default for logging.
+pub const DEFAULT_CONSOLE_INDEX: usize = 0;
 
-/// 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);
-}
+/// Index of the console used by default for emergency logging.
+pub const DEFAULT_EMERGENCY_CONSOLE_INDEX: usize = DEFAULT_CONSOLE_INDEX;
 
-/// Writes a string to the console.
+/// Initialises the global instance(s) of the UART driver.
 ///
-/// Panics if [`init`] was not called first.
-pub(crate) fn write_str(s: &str) {
-    CONSOLE.lock().as_mut().unwrap().write_str(s).unwrap();
-}
-
-/// Writes a formatted string to the console.
+/// This must be called before using the `print!` and `println!` macros.
 ///
-/// Panics if [`init`] was not called first.
-pub(crate) fn write_args(format_args: Arguments) {
-    write(CONSOLE.lock().as_mut().unwrap(), format_args).unwrap();
+/// # Safety
+///
+/// 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 = 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) });
+    }
 }
 
-/// Reinitializes the UART driver and writes a string to it.
+/// 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 mut guard = CONSOLES[n].lock();
+    let uart = guard.as_mut().unwrap();
+
+    write(uart, format_args).unwrap();
+    let _ = uart.write_str("\n");
+}
+
+/// 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 emergency_write_str(s: &str) {
-    let mut uart = create();
-    let _ = uart.write_str(s);
-}
+pub fn ewriteln(n: usize, format_args: Arguments) {
+    let Some(cell) = ADDRESSES[n].try_lock() else { return };
+    let Some(addr) = cell.get() else { return };
 
-/// Reinitializes the UART driver and writes a formatted string 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 emergency_write_args(format_args: Arguments) {
-    let mut uart = create();
+    // 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");
 }
 
+/// 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.
+#[macro_export]
+macro_rules! console_writeln {
+    ($n:expr, $($arg:tt)*) => ({
+        $crate::console::writeln($n, format_args!($($arg)*))
+    })
+}
+
+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.
 macro_rules! println {
-    () => ($crate::console::write_str("\n"));
     ($($arg:tt)*) => ({
-        $crate::console::write_args(format_args!($($arg)*))};
-        $crate::console::write_str("\n");
-    );
+        $crate::console::console_writeln!($crate::console::DEFAULT_CONSOLE_INDEX, $($arg)*)
+    })
 }
 
 pub(crate) use println; // Make it available in this crate.
 
-/// Prints the given string to the console in an emergency, such as an exception handler.
-///
-/// Never panics.
-#[macro_export]
-macro_rules! eprint {
-    ($($arg:tt)*) => ($crate::console::emergency_write_args(format_args!($($arg)*)));
-}
-
 /// 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 {
-    () => ($crate::console::emergency_write_str("\n"));
     ($($arg:tt)*) => ({
-        $crate::console::emergency_write_args(format_args!($($arg)*))};
-        $crate::console::emergency_write_str("\n");
-    );
+        $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 dedc6ae..ad633ed 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -16,7 +16,7 @@
 
 use crate::{
     bionic, console, heap, hyp,
-    layout::UART_PAGE_ADDR,
+    layout::{UART_ADDRESSES, UART_PAGE_ADDR},
     logger,
     memory::{SIZE_16KB, SIZE_4KB},
     power::{reboot, shutdown},
@@ -26,8 +26,6 @@
 use static_assertions::const_assert_eq;
 
 fn try_console_init() -> Result<(), hyp::Error> {
-    console::init();
-
     if let Some(mmio_guard) = hyp::get_mmio_guard() {
         mmio_guard.enroll()?;
 
@@ -49,6 +47,9 @@
         mmio_guard.map(UART_PAGE_ADDR)?;
     }
 
+    // SAFETY: UART_PAGE is mapped at stage-1 (see entry.S) and was just MMIO-guarded.
+    unsafe { console::init(&UART_ADDRESSES) };
+
     Ok(())
 }
 
diff --git a/vmbase/src/layout.rs b/vmbase/src/layout.rs
index 993141d..5ac435f 100644
--- a/vmbase/src/layout.rs
+++ b/vmbase/src/layout.rs
@@ -16,7 +16,6 @@
 
 pub mod crosvm;
 
-use crate::console;
 use crate::linker::__stack_chk_guard;
 use crate::memory::{page_4kb_of, PAGE_SIZE};
 use aarch64_paging::paging::VirtualAddress;
@@ -27,9 +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 addresses of the UART devices.
+///
+/// See SERIAL_ADDR in https://crosvm.dev/book/appendix/memory_layout.html#common-layout.
+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(console::BASE_ADDRESS));
+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]