pvmfw: Issue MMIO_GUARD_MAP for UART

Share the MMIO page containing the UART with the VMM.

When handling a synchronous exception, don't print to the UART if it
would cause re-entering the handler.

Bug: 237371962
Test: m pvmfw_img && flash pvmfw.img && access UART
Change-Id: Ia384f2e3a15b40b5e67cfafd50e571848fd5a126
diff --git a/pvmfw/src/exceptions.rs b/pvmfw/src/exceptions.rs
index 596ecc7..0fb2911 100644
--- a/pvmfw/src/exceptions.rs
+++ b/pvmfw/src/exceptions.rs
@@ -14,14 +14,23 @@
 
 //! Exception handlers.
 
+use crate::helpers::page_4kb_of;
 use core::arch::asm;
+use vmbase::console;
 use vmbase::{console::emergency_write_str, eprintln, power::reboot};
 
+const ESR_32BIT_EXT_DABT: u64 = 0x96000010;
+const UART_PAGE: u64 = page_4kb_of(console::BASE_ADDRESS as u64);
+
 #[no_mangle]
 extern "C" fn sync_exception_current(_elr: u64, _spsr: u64) {
     let esr = read_esr();
-    emergency_write_str("sync_exception_current\n");
-    print_esr(esr);
+    let far = read_far();
+    // Don't print to the UART if we're handling the exception it could raise.
+    if esr != ESR_32BIT_EXT_DABT || page_4kb_of(far) != UART_PAGE {
+        emergency_write_str("sync_exception_current\n");
+        print_esr(esr);
+    }
     reboot();
 }
 
@@ -86,3 +95,12 @@
 fn print_esr(esr: u64) {
     eprintln!("esr={:#08x}", esr);
 }
+
+#[inline]
+fn read_far() -> u64 {
+    let mut far: u64;
+    unsafe {
+        asm!("mrs {far}, far_el1", far = out(reg) far);
+    }
+    far
+}
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
new file mode 100644
index 0000000..781c1ac
--- /dev/null
+++ b/pvmfw/src/helpers.rs
@@ -0,0 +1,36 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Miscellaneous helper functions.
+
+/// Computes the address of the page containing a given address.
+pub const fn page_of(addr: u64, page_size: u64) -> u64 {
+    addr & !(page_size - 1)
+}
+
+/// Validates a page size and computes the address of the page containing a given address.
+pub const fn checked_page_of(addr: u64, page_size: u64) -> Option<u64> {
+    if page_size.is_power_of_two() {
+        Some(page_of(addr, page_size))
+    } else {
+        None
+    }
+}
+
+/// Computes the address of the 4KiB page containing a given address.
+pub const fn page_4kb_of(addr: u64) -> u64 {
+    const PAGE_SIZE: u64 = 4 << 10;
+
+    page_of(addr, PAGE_SIZE)
+}
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 5f918fb..eb97961 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -18,25 +18,36 @@
 #![no_std]
 
 mod exceptions;
+mod helpers;
+mod smccc;
 
 use core::fmt;
+use helpers::checked_page_of;
 
-use vmbase::{main, power::reboot, println};
+use vmbase::{console, main, power::reboot, println};
 
 #[derive(Debug, Clone)]
-enum Error {}
+enum Error {
+    /// Failed to configure the UART; no logs available.
+    FailedUartSetup,
+}
 
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        #[allow(clippy::match_single_binding)]
         let msg = match self {
-            _ => "",
+            Self::FailedUartSetup => "Failed to configure the UART",
         };
         write!(f, "{}", msg)
     }
 }
 
 fn main(fdt_address: u64, payload_start: u64, payload_size: u64, arg3: u64) -> Result<(), Error> {
+    // We need to inform the hypervisor that the MMIO page containing the UART may be shared back.
+    let uart = console::BASE_ADDRESS as u64;
+    let mmio_granule = smccc::mmio_guard_info().map_err(|_| Error::FailedUartSetup)?;
+    let uart_page = checked_page_of(uart, mmio_granule).ok_or(Error::FailedUartSetup)?;
+    smccc::mmio_guard_map(uart_page).map_err(|_| Error::FailedUartSetup)?;
+
     println!("pVM firmware");
     println!(
         "fdt_address={:#018x}, payload_start={:#018x}, payload_size={:#018x}, x3={:#018x}",
diff --git a/pvmfw/src/smccc.rs b/pvmfw/src/smccc.rs
new file mode 100644
index 0000000..e3a2b05
--- /dev/null
+++ b/pvmfw/src/smccc.rs
@@ -0,0 +1,115 @@
+// Copyright 2022, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use core::fmt;
+
+// TODO(b/245889995): use psci-0.1.1 crate
+#[inline(always)]
+fn hvc64(function: u32, args: [u64; 17]) -> [u64; 18] {
+    #[cfg(target_arch = "aarch64")]
+    unsafe {
+        let mut ret = [0; 18];
+
+        core::arch::asm!(
+            "hvc #0",
+            inout("x0") function as u64 => ret[0],
+            inout("x1") args[0] => ret[1],
+            inout("x2") args[1] => ret[2],
+            inout("x3") args[2] => ret[3],
+            inout("x4") args[3] => ret[4],
+            inout("x5") args[4] => ret[5],
+            inout("x6") args[5] => ret[6],
+            inout("x7") args[6] => ret[7],
+            inout("x8") args[7] => ret[8],
+            inout("x9") args[8] => ret[9],
+            inout("x10") args[9] => ret[10],
+            inout("x11") args[10] => ret[11],
+            inout("x12") args[11] => ret[12],
+            inout("x13") args[12] => ret[13],
+            inout("x14") args[13] => ret[14],
+            inout("x15") args[14] => ret[15],
+            inout("x16") args[15] => ret[16],
+            inout("x17") args[16] => ret[17],
+            options(nomem, nostack)
+        );
+
+        ret
+    }
+}
+
+/// Standard SMCCC error values as described in DEN 0028E.
+#[derive(Debug, Clone)]
+pub enum Error {
+    /// The call is not supported by the implementation.
+    NotSupported,
+    /// The call is deemed not required by the implementation.
+    NotRequired,
+    /// One of the call parameters has a non-supported value.
+    InvalidParameter,
+    /// Negative values indicate error.
+    Unknown(i64),
+    /// The call returned a positive value when 0 was expected.
+    Unexpected(u64),
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::NotSupported => write!(f, "SMCCC call not supported"),
+            Self::NotRequired => write!(f, "SMCCC call not required"),
+            Self::InvalidParameter => write!(f, "SMCCC call received non-supported value"),
+            Self::Unexpected(v) => write!(f, "Unexpected SMCCC return value '{}'", v),
+            Self::Unknown(e) => write!(f, "Unknown SMCCC return value '{}'", e),
+        }
+    }
+}
+
+fn check_smccc_err(ret: i64) -> Result<(), Error> {
+    match check_smccc_value(ret)? {
+        0 => Ok(()),
+        v => Err(Error::Unexpected(v)),
+    }
+}
+
+fn check_smccc_value(ret: i64) -> Result<u64, Error> {
+    match ret {
+        x if x >= 0 => Ok(ret as u64),
+        -1 => Err(Error::NotSupported),
+        -2 => Err(Error::NotRequired),
+        -3 => Err(Error::InvalidParameter),
+        _ => Err(Error::Unknown(ret)),
+    }
+}
+
+const VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID: u32 = 0xc6000005;
+const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
+
+/// Issue pKVM-specific MMIO_GUARD_INFO HVC64.
+pub fn mmio_guard_info() -> Result<u64, Error> {
+    let args = [0u64; 17];
+
+    let res = hvc64(VENDOR_HYP_KVM_MMIO_GUARD_INFO_FUNC_ID, args);
+
+    check_smccc_value(res[0] as i64)
+}
+
+/// Issue pKVM-specific MMIO_GUARD_MAP HVC64.
+pub fn mmio_guard_map(ipa: u64) -> Result<(), Error> {
+    let mut args = [0u64; 17];
+    args[0] = ipa;
+
+    let res = hvc64(VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID, args);
+
+    check_smccc_err(res[0] as i64)
+}