Merge changes I5ac315ed,I57d5a764,I82360003,I4ff012ac,Ibac59fc1, ... into main

* changes:
  vmbase: Move Trng internals to arch::aarch64
  vmbase: Export VirtualAddress from arch
  vmbase: Separate console form aarch64 UART implementation
  vmbase: uart: Create abstraction layer over register write
  pvmfw: Move aarch64 fn `jump_to_payload` to a separate module
  pvmfw: Move aarch64 exceptions handling to new `arch` module
  vmbase: Move exceptions to aarch64 directories
  vmbase: Move hvc to aarch64
  vmbase: Move dbm to aarch64
  vmbase: Move linker to aarch64
  vmbase: Move page table to aarch64
  vmbase: Move crosvm layout to aarch64
  vmbase: aarch64: Move power to platform
  libvmbase: Separate aarch64 asm files
  pvmfw: Compile aarch64 specifics conditionally
  vmbase: Use aarch64 specific libs conditionally
diff --git a/guest/pvmfw/Android.bp b/guest/pvmfw/Android.bp
index 4ef57a6..6f113c8 100644
--- a/guest/pvmfw/Android.bp
+++ b/guest/pvmfw/Android.bp
@@ -11,7 +11,6 @@
         "legacy",
     ],
     rustlibs: [
-        "libaarch64_paging",
         "libbssl_avf_nostd",
         "libcbor_util_nostd",
         "libciborium_nostd",
@@ -24,8 +23,6 @@
         "libpvmfw_avb_nostd",
         "libpvmfw_embedded_key",
         "libpvmfw_fdt_template",
-        "libservice_vm_version",
-        "libsmccc",
         "libstatic_assertions",
         "libtinyvec_nostd",
         "libuuid_nostd",
@@ -34,6 +31,15 @@
         "libzerocopy_nostd",
         "libzeroize_nostd",
     ],
+    target: {
+        android_arm64: {
+            rustlibs: [
+                "libaarch64_paging",
+                "libsmccc",
+                "libservice_vm_version",
+            ],
+        },
+    },
 }
 
 // Generates an empty file.
@@ -270,17 +276,21 @@
 cc_binary {
     name: "pvmfw",
     defaults: ["vmbase_elf_defaults"],
-    srcs: [
-        "idmap.S",
-    ],
     static_libs: [
         "libpvmfw",
         "libvmbase_dice_clear_memory",
     ],
-    linker_scripts: [
-        "image.ld",
-        ":vmbase_sections",
-    ],
+    target: {
+        android_arm64: {
+            srcs: [
+                "asm/aarch64/idmap.S",
+            ],
+            linker_scripts: [
+                "asm/aarch64/image.ld",
+                ":vmbase_sections",
+            ],
+        },
+    },
     // `installable: false` is inherited from vmbase_elf_defaults, and that
     // hides this module from Make, which makes it impossible for the Make world
     // to place the unstripped binary to the symbols directory. Marking back as
diff --git a/guest/pvmfw/idmap.S b/guest/pvmfw/asm/aarch64/idmap.S
similarity index 100%
rename from guest/pvmfw/idmap.S
rename to guest/pvmfw/asm/aarch64/idmap.S
diff --git a/guest/pvmfw/image.ld b/guest/pvmfw/asm/aarch64/image.ld
similarity index 100%
rename from guest/pvmfw/image.ld
rename to guest/pvmfw/asm/aarch64/image.ld
diff --git a/guest/pvmfw/src/arch.rs b/guest/pvmfw/src/arch.rs
new file mode 100644
index 0000000..1bbf4d4
--- /dev/null
+++ b/guest/pvmfw/src/arch.rs
@@ -0,0 +1,21 @@
+// Copyright 2024, 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.
+
+//! Module providing Low-level platform specific implementations
+
+#[cfg(target_arch = "aarch64")]
+mod aarch64;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::payload;
diff --git a/guest/pvmfw/src/arch/aarch64.rs b/guest/pvmfw/src/arch/aarch64.rs
new file mode 100644
index 0000000..171ba64
--- /dev/null
+++ b/guest/pvmfw/src/arch/aarch64.rs
@@ -0,0 +1,18 @@
+// Copyright 2024, 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.
+
+//! aarch64 platform specific code
+
+pub mod exceptions;
+pub mod payload;
diff --git a/guest/pvmfw/src/exceptions.rs b/guest/pvmfw/src/arch/aarch64/exceptions.rs
similarity index 93%
rename from guest/pvmfw/src/exceptions.rs
rename to guest/pvmfw/src/arch/aarch64/exceptions.rs
index c16e637..4c867fb 100644
--- a/guest/pvmfw/src/exceptions.rs
+++ b/guest/pvmfw/src/arch/aarch64/exceptions.rs
@@ -15,10 +15,10 @@
 //! Exception handlers.
 
 use vmbase::{
-    eprintln,
-    exceptions::{handle_permission_fault, handle_translation_fault},
-    exceptions::{ArmException, Esr, HandleExceptionError},
-    logger,
+    arch::aarch64::exceptions::{
+        handle_permission_fault, handle_translation_fault, ArmException, Esr, HandleExceptionError,
+    },
+    eprintln, logger,
     power::reboot,
     read_sysreg,
 };
diff --git a/guest/pvmfw/src/arch/aarch64/payload.rs b/guest/pvmfw/src/arch/aarch64/payload.rs
new file mode 100644
index 0000000..0da8297
--- /dev/null
+++ b/guest/pvmfw/src/arch/aarch64/payload.rs
@@ -0,0 +1,177 @@
+// 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.
+
+//! ARM64 low-level payload entry point
+
+use crate::memory::MemorySlices;
+use core::arch::asm;
+use core::mem::size_of;
+use vmbase::util::RangeExt as _;
+use vmbase::{arch::aarch64::min_dcache_line_size, layout, memory::deactivate_dynamic_page_tables};
+
+/// Function boot payload after cleaning all secret from pvmfw memory
+pub fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! {
+    let fdt_address = slices.fdt.as_ptr() as usize;
+    let bcc = slices
+        .dice_chain
+        .map(|slice| {
+            let r = slice.as_ptr_range();
+            (r.start as usize)..(r.end as usize)
+        })
+        .expect("Missing DICE chain");
+
+    deactivate_dynamic_page_tables();
+
+    const ASM_STP_ALIGN: usize = size_of::<u64>() * 2;
+    const SCTLR_EL1_RES1: u64 = (0b11 << 28) | (0b101 << 20) | (0b1 << 11);
+    // Stage 1 instruction access cacheability is unaffected.
+    const SCTLR_EL1_I: u64 = 0b1 << 12;
+    // SETEND instruction disabled at EL0 in aarch32 mode.
+    const SCTLR_EL1_SED: u64 = 0b1 << 8;
+    // Various IT instructions are disabled at EL0 in aarch32 mode.
+    const SCTLR_EL1_ITD: u64 = 0b1 << 7;
+
+    const SCTLR_EL1_VAL: u64 = SCTLR_EL1_RES1 | SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_I;
+
+    let scratch = layout::data_bss_range();
+
+    assert_ne!(scratch.end - scratch.start, 0, "scratch memory is empty.");
+    assert_eq!(scratch.start.0 % ASM_STP_ALIGN, 0, "scratch memory is misaligned.");
+    assert_eq!(scratch.end.0 % ASM_STP_ALIGN, 0, "scratch memory is misaligned.");
+
+    assert!(bcc.is_within(&(scratch.start.0..scratch.end.0)));
+    assert_eq!(bcc.start % ASM_STP_ALIGN, 0, "Misaligned guest BCC.");
+    assert_eq!(bcc.end % ASM_STP_ALIGN, 0, "Misaligned guest BCC.");
+
+    let stack = layout::stack_range();
+
+    assert_ne!(stack.end - stack.start, 0, "stack region is empty.");
+    assert_eq!(stack.start.0 % ASM_STP_ALIGN, 0, "Misaligned stack region.");
+    assert_eq!(stack.end.0 % ASM_STP_ALIGN, 0, "Misaligned stack region.");
+
+    let eh_stack = layout::eh_stack_range();
+
+    assert_ne!(eh_stack.end - eh_stack.start, 0, "EH stack region is empty.");
+    assert_eq!(eh_stack.start.0 % ASM_STP_ALIGN, 0, "Misaligned EH stack region.");
+    assert_eq!(eh_stack.end.0 % ASM_STP_ALIGN, 0, "Misaligned EH stack region.");
+
+    // Zero all memory that could hold secrets and that can't be safely written to from Rust.
+    // Disable the exception vector, caches and page table and then jump to the payload at the
+    // given address, passing it the given FDT pointer.
+    //
+    // SAFETY: We're exiting pvmfw by passing the register values we need to a noreturn asm!().
+    unsafe {
+        asm!(
+            "cmp {scratch}, {bcc}",
+            "b.hs 1f",
+
+            // Zero .data & .bss until BCC.
+            "0: stp xzr, xzr, [{scratch}], 16",
+            "cmp {scratch}, {bcc}",
+            "b.lo 0b",
+
+            "1:",
+            // Skip BCC.
+            "mov {scratch}, {bcc_end}",
+            "cmp {scratch}, {scratch_end}",
+            "b.hs 1f",
+
+            // Keep zeroing .data & .bss.
+            "0: stp xzr, xzr, [{scratch}], 16",
+            "cmp {scratch}, {scratch_end}",
+            "b.lo 0b",
+
+            "1:",
+            // Flush d-cache over .data & .bss (including BCC).
+            "0: dc cvau, {cache_line}",
+            "add {cache_line}, {cache_line}, {dcache_line_size}",
+            "cmp {cache_line}, {scratch_end}",
+            "b.lo 0b",
+
+            "mov {cache_line}, {stack}",
+            // Zero stack region.
+            "0: stp xzr, xzr, [{stack}], 16",
+            "cmp {stack}, {stack_end}",
+            "b.lo 0b",
+
+            // Flush d-cache over stack region.
+            "0: dc cvau, {cache_line}",
+            "add {cache_line}, {cache_line}, {dcache_line_size}",
+            "cmp {cache_line}, {stack_end}",
+            "b.lo 0b",
+
+            "mov {cache_line}, {eh_stack}",
+            // Zero EH stack region.
+            "0: stp xzr, xzr, [{eh_stack}], 16",
+            "cmp {eh_stack}, {eh_stack_end}",
+            "b.lo 0b",
+
+            // Flush d-cache over EH stack region.
+            "0: dc cvau, {cache_line}",
+            "add {cache_line}, {cache_line}, {dcache_line_size}",
+            "cmp {cache_line}, {eh_stack_end}",
+            "b.lo 0b",
+
+            "msr sctlr_el1, {sctlr_el1_val}",
+            "isb",
+            "mov x1, xzr",
+            "mov x2, xzr",
+            "mov x3, xzr",
+            "mov x4, xzr",
+            "mov x5, xzr",
+            "mov x6, xzr",
+            "mov x7, xzr",
+            "mov x8, xzr",
+            "mov x9, xzr",
+            "mov x10, xzr",
+            "mov x11, xzr",
+            "mov x12, xzr",
+            "mov x13, xzr",
+            "mov x14, xzr",
+            "mov x15, xzr",
+            "mov x16, xzr",
+            "mov x17, xzr",
+            "mov x18, xzr",
+            "mov x19, xzr",
+            "mov x20, xzr",
+            "mov x21, xzr",
+            "mov x22, xzr",
+            "mov x23, xzr",
+            "mov x24, xzr",
+            "mov x25, xzr",
+            "mov x26, xzr",
+            "mov x27, xzr",
+            "mov x28, xzr",
+            "mov x29, xzr",
+            "msr ttbr0_el1, xzr",
+            // Ensure that CMOs have completed before entering payload.
+            "dsb nsh",
+            "br x30",
+            sctlr_el1_val = in(reg) SCTLR_EL1_VAL,
+            bcc = in(reg) u64::try_from(bcc.start).unwrap(),
+            bcc_end = in(reg) u64::try_from(bcc.end).unwrap(),
+            cache_line = in(reg) u64::try_from(scratch.start.0).unwrap(),
+            scratch = in(reg) u64::try_from(scratch.start.0).unwrap(),
+            scratch_end = in(reg) u64::try_from(scratch.end.0).unwrap(),
+            stack = in(reg) u64::try_from(stack.start.0).unwrap(),
+            stack_end = in(reg) u64::try_from(stack.end.0).unwrap(),
+            eh_stack = in(reg) u64::try_from(eh_stack.start.0).unwrap(),
+            eh_stack_end = in(reg) u64::try_from(eh_stack.end.0).unwrap(),
+            dcache_line_size = in(reg) u64::try_from(min_dcache_line_size()).unwrap(),
+            in("x0") u64::try_from(fdt_address).unwrap(),
+            in("x30") u64::try_from(entrypoint).unwrap(),
+            options(noreturn),
+        );
+    };
+}
diff --git a/guest/pvmfw/src/entry.rs b/guest/pvmfw/src/entry.rs
index 862fb1d..8ada6a1 100644
--- a/guest/pvmfw/src/entry.rs
+++ b/guest/pvmfw/src/entry.rs
@@ -14,21 +14,18 @@
 
 //! Low-level entry and exit points of pvmfw.
 
+use crate::arch::payload::jump_to_payload;
 use crate::config;
 use crate::memory::MemorySlices;
-use core::arch::asm;
-use core::mem::size_of;
 use core::slice;
 use log::error;
 use log::warn;
 use log::LevelFilter;
-use vmbase::util::RangeExt as _;
 use vmbase::{
-    arch::aarch64::min_dcache_line_size,
-    configure_heap, console_writeln, layout, limit_stack_size, main,
+    configure_heap, console_writeln, limit_stack_size, main,
     memory::{
-        deactivate_dynamic_page_tables, map_image_footer, unshare_all_memory,
-        unshare_all_mmio_except_uart, unshare_uart, MemoryTrackerError, SIZE_128KB, SIZE_4KB,
+        map_image_footer, unshare_all_memory, unshare_all_mmio_except_uart, unshare_uart,
+        MemoryTrackerError, SIZE_128KB, SIZE_4KB,
     },
     power::reboot,
 };
@@ -173,161 +170,6 @@
     }
 }
 
-fn jump_to_payload(entrypoint: usize, slices: &MemorySlices) -> ! {
-    let fdt_address = slices.fdt.as_ptr() as usize;
-    let bcc = slices
-        .dice_chain
-        .map(|slice| {
-            let r = slice.as_ptr_range();
-            (r.start as usize)..(r.end as usize)
-        })
-        .expect("Missing DICE chain");
-
-    deactivate_dynamic_page_tables();
-
-    const ASM_STP_ALIGN: usize = size_of::<u64>() * 2;
-    const SCTLR_EL1_RES1: u64 = (0b11 << 28) | (0b101 << 20) | (0b1 << 11);
-    // Stage 1 instruction access cacheability is unaffected.
-    const SCTLR_EL1_I: u64 = 0b1 << 12;
-    // SETEND instruction disabled at EL0 in aarch32 mode.
-    const SCTLR_EL1_SED: u64 = 0b1 << 8;
-    // Various IT instructions are disabled at EL0 in aarch32 mode.
-    const SCTLR_EL1_ITD: u64 = 0b1 << 7;
-
-    const SCTLR_EL1_VAL: u64 = SCTLR_EL1_RES1 | SCTLR_EL1_ITD | SCTLR_EL1_SED | SCTLR_EL1_I;
-
-    let scratch = layout::data_bss_range();
-
-    assert_ne!(scratch.end - scratch.start, 0, "scratch memory is empty.");
-    assert_eq!(scratch.start.0 % ASM_STP_ALIGN, 0, "scratch memory is misaligned.");
-    assert_eq!(scratch.end.0 % ASM_STP_ALIGN, 0, "scratch memory is misaligned.");
-
-    assert!(bcc.is_within(&(scratch.start.0..scratch.end.0)));
-    assert_eq!(bcc.start % ASM_STP_ALIGN, 0, "Misaligned guest BCC.");
-    assert_eq!(bcc.end % ASM_STP_ALIGN, 0, "Misaligned guest BCC.");
-
-    let stack = layout::stack_range();
-
-    assert_ne!(stack.end - stack.start, 0, "stack region is empty.");
-    assert_eq!(stack.start.0 % ASM_STP_ALIGN, 0, "Misaligned stack region.");
-    assert_eq!(stack.end.0 % ASM_STP_ALIGN, 0, "Misaligned stack region.");
-
-    let eh_stack = layout::eh_stack_range();
-
-    assert_ne!(eh_stack.end - eh_stack.start, 0, "EH stack region is empty.");
-    assert_eq!(eh_stack.start.0 % ASM_STP_ALIGN, 0, "Misaligned EH stack region.");
-    assert_eq!(eh_stack.end.0 % ASM_STP_ALIGN, 0, "Misaligned EH stack region.");
-
-    // Zero all memory that could hold secrets and that can't be safely written to from Rust.
-    // Disable the exception vector, caches and page table and then jump to the payload at the
-    // given address, passing it the given FDT pointer.
-    //
-    // SAFETY: We're exiting pvmfw by passing the register values we need to a noreturn asm!().
-    unsafe {
-        asm!(
-            "cmp {scratch}, {bcc}",
-            "b.hs 1f",
-
-            // Zero .data & .bss until BCC.
-            "0: stp xzr, xzr, [{scratch}], 16",
-            "cmp {scratch}, {bcc}",
-            "b.lo 0b",
-
-            "1:",
-            // Skip BCC.
-            "mov {scratch}, {bcc_end}",
-            "cmp {scratch}, {scratch_end}",
-            "b.hs 1f",
-
-            // Keep zeroing .data & .bss.
-            "0: stp xzr, xzr, [{scratch}], 16",
-            "cmp {scratch}, {scratch_end}",
-            "b.lo 0b",
-
-            "1:",
-            // Flush d-cache over .data & .bss (including BCC).
-            "0: dc cvau, {cache_line}",
-            "add {cache_line}, {cache_line}, {dcache_line_size}",
-            "cmp {cache_line}, {scratch_end}",
-            "b.lo 0b",
-
-            "mov {cache_line}, {stack}",
-            // Zero stack region.
-            "0: stp xzr, xzr, [{stack}], 16",
-            "cmp {stack}, {stack_end}",
-            "b.lo 0b",
-
-            // Flush d-cache over stack region.
-            "0: dc cvau, {cache_line}",
-            "add {cache_line}, {cache_line}, {dcache_line_size}",
-            "cmp {cache_line}, {stack_end}",
-            "b.lo 0b",
-
-            "mov {cache_line}, {eh_stack}",
-            // Zero EH stack region.
-            "0: stp xzr, xzr, [{eh_stack}], 16",
-            "cmp {eh_stack}, {eh_stack_end}",
-            "b.lo 0b",
-
-            // Flush d-cache over EH stack region.
-            "0: dc cvau, {cache_line}",
-            "add {cache_line}, {cache_line}, {dcache_line_size}",
-            "cmp {cache_line}, {eh_stack_end}",
-            "b.lo 0b",
-
-            "msr sctlr_el1, {sctlr_el1_val}",
-            "isb",
-            "mov x1, xzr",
-            "mov x2, xzr",
-            "mov x3, xzr",
-            "mov x4, xzr",
-            "mov x5, xzr",
-            "mov x6, xzr",
-            "mov x7, xzr",
-            "mov x8, xzr",
-            "mov x9, xzr",
-            "mov x10, xzr",
-            "mov x11, xzr",
-            "mov x12, xzr",
-            "mov x13, xzr",
-            "mov x14, xzr",
-            "mov x15, xzr",
-            "mov x16, xzr",
-            "mov x17, xzr",
-            "mov x18, xzr",
-            "mov x19, xzr",
-            "mov x20, xzr",
-            "mov x21, xzr",
-            "mov x22, xzr",
-            "mov x23, xzr",
-            "mov x24, xzr",
-            "mov x25, xzr",
-            "mov x26, xzr",
-            "mov x27, xzr",
-            "mov x28, xzr",
-            "mov x29, xzr",
-            "msr ttbr0_el1, xzr",
-            // Ensure that CMOs have completed before entering payload.
-            "dsb nsh",
-            "br x30",
-            sctlr_el1_val = in(reg) SCTLR_EL1_VAL,
-            bcc = in(reg) u64::try_from(bcc.start).unwrap(),
-            bcc_end = in(reg) u64::try_from(bcc.end).unwrap(),
-            cache_line = in(reg) u64::try_from(scratch.start.0).unwrap(),
-            scratch = in(reg) u64::try_from(scratch.start.0).unwrap(),
-            scratch_end = in(reg) u64::try_from(scratch.end.0).unwrap(),
-            stack = in(reg) u64::try_from(stack.start.0).unwrap(),
-            stack_end = in(reg) u64::try_from(stack.end.0).unwrap(),
-            eh_stack = in(reg) u64::try_from(eh_stack.start.0).unwrap(),
-            eh_stack_end = in(reg) u64::try_from(eh_stack.end.0).unwrap(),
-            dcache_line_size = in(reg) u64::try_from(min_dcache_line_size()).unwrap(),
-            in("x0") u64::try_from(fdt_address).unwrap(),
-            in("x30") u64::try_from(entrypoint).unwrap(),
-            options(noreturn),
-        );
-    };
-}
-
 fn get_appended_data_slice() -> Result<&'static mut [u8], MemoryTrackerError> {
     let range = map_image_footer()?;
     // SAFETY: This region was just mapped for the first time (as map_image_footer() didn't fail)
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index afa64e0..9c67be8 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -19,13 +19,13 @@
 
 extern crate alloc;
 
+mod arch;
 mod bcc;
 mod bootargs;
 mod config;
 mod device_assignment;
 mod dice;
 mod entry;
-mod exceptions;
 mod fdt;
 mod gpt;
 mod instance;
diff --git a/guest/rialto/src/exceptions.rs b/guest/rialto/src/exceptions.rs
index 8899796..467a3a6 100644
--- a/guest/rialto/src/exceptions.rs
+++ b/guest/rialto/src/exceptions.rs
@@ -15,10 +15,10 @@
 //! Exception handlers.
 
 use vmbase::{
-    eprintln,
-    exceptions::{handle_permission_fault, handle_translation_fault},
-    exceptions::{ArmException, Esr, HandleExceptionError},
-    logger,
+    arch::aarch64::exceptions::{
+        handle_permission_fault, handle_translation_fault, ArmException, Esr, HandleExceptionError,
+    },
+    eprintln, logger,
     power::reboot,
     read_sysreg,
 };
diff --git a/guest/vmbase_example/src/main.rs b/guest/vmbase_example/src/main.rs
index b7d2f95..8723a55 100644
--- a/guest/vmbase_example/src/main.rs
+++ b/guest/vmbase_example/src/main.rs
@@ -30,11 +30,12 @@
 use log::{debug, error, info, trace, warn, LevelFilter};
 use spin::mutex::SpinMutex;
 use vmbase::{
+    arch::linker,
     bionic, configure_heap,
     fdt::pci::PciInfo,
     generate_image_header,
     layout::crosvm::FDT_MAX_SIZE,
-    linker, logger, main,
+    logger, main,
     memory::{deactivate_dynamic_page_tables, map_data, SIZE_64KB},
 };
 
diff --git a/libs/libvmbase/Android.bp b/libs/libvmbase/Android.bp
index 7465508..2d0294e 100644
--- a/libs/libvmbase/Android.bp
+++ b/libs/libvmbase/Android.bp
@@ -77,14 +77,12 @@
     crate_name: "vmbase",
     srcs: ["src/lib.rs"],
     rustlibs: [
-        "libaarch64_paging",
         "libbuddy_system_allocator",
         "libcfg_if",
         "libhypervisor_backends",
         "liblibfdt_nostd",
         "liblog_rust_nostd",
         "libonce_cell_nostd",
-        "libsmccc",
         "libspin_nostd",
         "libstatic_assertions",
         "libthiserror_nostd",
@@ -97,21 +95,34 @@
     whole_static_libs: [
         "librust_baremetal",
     ],
-    // TODO(b/277859415, b/277860860): Drop "compat_android_13".
-    features: [
-        "compat_android_13",
-        "cpu_feat_hafdbs",
-    ],
+    target: {
+        android_arm64: {
+            rustlibs: [
+                "libaarch64_paging",
+                "libsmccc",
+            ],
+            // TODO(b/277859415, b/277860860): Drop "compat_android_13".
+            features: [
+                "compat_android_13",
+                "cpu_feat_hafdbs",
+            ],
+        },
+    },
 }
 
 cc_library_static {
     name: "libvmbase_entry",
     defaults: ["vmbase_cc_defaults"],
-    srcs: [
-        "entry.S",
-        "exceptions.S",
-        "exceptions_panic.S",
-    ],
+    srcs: [],
+    target: {
+        android_arm64: {
+            srcs: [
+                "asm/aarch64/entry.S",
+                "asm/aarch64/exceptions.S",
+                "asm/aarch64/exceptions_panic.S",
+            ],
+        },
+    },
 }
 
 filegroup {
diff --git a/libs/libvmbase/common.h b/libs/libvmbase/asm/aarch64/common.h
similarity index 100%
rename from libs/libvmbase/common.h
rename to libs/libvmbase/asm/aarch64/common.h
diff --git a/libs/libvmbase/entry.S b/libs/libvmbase/asm/aarch64/entry.S
similarity index 99%
rename from libs/libvmbase/entry.S
rename to libs/libvmbase/asm/aarch64/entry.S
index 9177a4a..6dffbab 100644
--- a/libs/libvmbase/entry.S
+++ b/libs/libvmbase/asm/aarch64/entry.S
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <common.h>
+#include "common.h"
 
 .set .L_MAIR_DEV_nGnRE,	0x04
 .set .L_MAIR_MEM_WBWA,	0xff
diff --git a/libs/libvmbase/exceptions.S b/libs/libvmbase/asm/aarch64/exceptions.S
similarity index 100%
rename from libs/libvmbase/exceptions.S
rename to libs/libvmbase/asm/aarch64/exceptions.S
diff --git a/libs/libvmbase/exceptions_panic.S b/libs/libvmbase/asm/aarch64/exceptions_panic.S
similarity index 98%
rename from libs/libvmbase/exceptions_panic.S
rename to libs/libvmbase/asm/aarch64/exceptions_panic.S
index 54735b2..d064c8d 100644
--- a/libs/libvmbase/exceptions_panic.S
+++ b/libs/libvmbase/asm/aarch64/exceptions_panic.S
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include <common.h>
+#include "common.h"
 
 /**
  * The following table is intended to trap any fault resulting from the very
diff --git a/libs/libvmbase/src/arch.rs b/libs/libvmbase/src/arch.rs
index 0348800..cc42939 100644
--- a/libs/libvmbase/src/arch.rs
+++ b/libs/libvmbase/src/arch.rs
@@ -17,6 +17,27 @@
 #[cfg(target_arch = "aarch64")]
 pub mod aarch64;
 
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::platform;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::layout;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::linker;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::dbm;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::rand;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64::uart;
+
+#[cfg(target_arch = "aarch64")]
+pub use aarch64_paging::paging::VirtualAddress;
+
 /// Write with well-defined compiled behavior.
 ///
 /// See https://github.com/rust-lang/rust/issues/131894
@@ -44,7 +65,6 @@
             let line_size = aarch64::min_dcache_line_size();
             let end = start + size;
             let start = crate::util::unchecked_align_down(start, line_size);
-
             for line in (start..end).step_by(line_size) {
                 crate::dc!("cvau", line);
             }
diff --git a/libs/libvmbase/src/arch/aarch64.rs b/libs/libvmbase/src/arch/aarch64.rs
index 5006aca..9c4d256 100644
--- a/libs/libvmbase/src/arch/aarch64.rs
+++ b/libs/libvmbase/src/arch/aarch64.rs
@@ -14,6 +14,16 @@
 
 //! Wrappers of assembly calls.
 
+pub mod dbm;
+pub mod exceptions;
+pub mod hvc;
+pub mod layout;
+pub mod linker;
+pub mod page_table;
+pub mod platform;
+pub mod rand;
+pub mod uart;
+
 /// Reads a value from a system register.
 #[macro_export]
 macro_rules! read_sysreg {
diff --git a/libs/libvmbase/src/memory/dbm.rs b/libs/libvmbase/src/arch/aarch64/dbm.rs
similarity index 93%
rename from libs/libvmbase/src/memory/dbm.rs
rename to libs/libvmbase/src/arch/aarch64/dbm.rs
index de43403..1c2190c 100644
--- a/libs/libvmbase/src/memory/dbm.rs
+++ b/libs/libvmbase/src/arch/aarch64/dbm.rs
@@ -14,14 +14,14 @@
 
 //! Hardware management of the access flag and dirty state.
 
-use super::page_table::PageTable;
+use crate::arch::aarch64::page_table::PageTable;
 use crate::arch::flush_region;
 use crate::{dsb, isb, read_sysreg, tlbi, write_sysreg};
 use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion};
 
 /// Sets whether the hardware management of access and dirty state is enabled with
 /// the given boolean.
-pub(super) fn set_dbm_enabled(enabled: bool) {
+pub fn set_dbm_enabled(enabled: bool) {
     if !dbm_available() {
         return;
     }
@@ -49,8 +49,9 @@
     read_sysreg!("id_aa64mmfr1_el1") & DBM_AVAILABLE != 0
 }
 
+#[allow(clippy::result_unit_err)]
 /// Flushes a memory range the descriptor refers to, if the descriptor is in writable-dirty state.
-pub(super) fn flush_dirty_range(
+pub fn flush_dirty_range(
     va_range: &MemoryRegion,
     desc: &Descriptor,
     _level: usize,
@@ -62,9 +63,10 @@
     Ok(())
 }
 
+#[allow(clippy::result_unit_err)]
 /// Clears read-only flag on a PTE, making it writable-dirty. Used when dirty state is managed
 /// in software to handle permission faults on read-only descriptors.
-pub(super) fn mark_dirty_block(
+pub fn mark_dirty_block(
     va_range: &MemoryRegion,
     desc: &mut Descriptor,
     _level: usize,
diff --git a/libs/libvmbase/src/exceptions.rs b/libs/libvmbase/src/arch/aarch64/exceptions.rs
similarity index 97%
rename from libs/libvmbase/src/exceptions.rs
rename to libs/libvmbase/src/arch/aarch64/exceptions.rs
index b04cb16..1868bf7 100644
--- a/libs/libvmbase/src/exceptions.rs
+++ b/libs/libvmbase/src/arch/aarch64/exceptions.rs
@@ -14,13 +14,11 @@
 
 //! Helper functions and structs for exception handlers.
 
+use crate::memory::{MemoryTrackerError, MEMORY};
 use crate::{
-    eprintln,
-    layout::UART_PAGE_ADDR,
-    memory::{page_4kb_of, MemoryTrackerError, MEMORY},
+    arch::aarch64::layout::UART_PAGE_ADDR, arch::VirtualAddress, eprintln, memory::page_4kb_of,
     read_sysreg,
 };
-use aarch64_paging::paging::VirtualAddress;
 use core::fmt;
 use core::result;
 
@@ -99,6 +97,7 @@
         }
     }
 }
+
 /// A struct representing an Armv8 exception.
 pub struct ArmException {
     /// The value of the exception syndrome register.
diff --git a/libs/libvmbase/src/hvc.rs b/libs/libvmbase/src/arch/aarch64/hvc.rs
similarity index 83%
rename from libs/libvmbase/src/hvc.rs
rename to libs/libvmbase/src/arch/aarch64/hvc.rs
index 1197143..b20f62d 100644
--- a/libs/libvmbase/src/hvc.rs
+++ b/libs/libvmbase/src/arch/aarch64/hvc.rs
@@ -14,6 +14,7 @@
 
 //! Wrappers around calls to the hypervisor.
 
+/// TRNG ARM specific module
 pub mod trng;
 use self::trng::Error;
 use smccc::{
@@ -21,12 +22,18 @@
     hvc64,
 };
 
+/// ARM HVC call number that will return TRNG version
 const ARM_SMCCC_TRNG_VERSION: u32 = 0x8400_0050;
+/// ARM TRNG feature hypercall number
 const ARM_SMCCC_TRNG_FEATURES: u32 = 0x8400_0051;
 #[allow(dead_code)]
+/// ARM SMCC TRNG get uuid hypercall number
 const ARM_SMCCC_TRNG_GET_UUID: u32 = 0x8400_0052;
 #[allow(dead_code)]
+/// ARM SMCC TRNG 32BIT random hypercall number
 const ARM_SMCCC_TRNG_RND32: u32 = 0x8400_0053;
+
+/// ARM SMCCC 64BIT random hypercall number
 pub const ARM_SMCCC_TRNG_RND64: u32 = 0xc400_0053;
 
 /// Returns the (major, minor) version tuple, as defined by the SMCCC TRNG.
@@ -37,8 +44,10 @@
     (version as u32 as i32).try_into()
 }
 
+/// Buffer for random number
 pub type TrngRng64Entropy = [u64; 3];
 
+/// Return hardware backed entropy from TRNG
 pub fn trng_rnd64(nbits: u64) -> trng::Result<TrngRng64Entropy> {
     let mut args = [0u64; 17];
     args[0] = nbits;
@@ -49,6 +58,7 @@
     Ok([regs[1], regs[2], regs[3]])
 }
 
+/// Return TRNG feature
 pub fn trng_features(fid: u32) -> trng::Result<u64> {
     let mut args = [0u64; 17];
     args[0] = fid as u64;
diff --git a/libs/libvmbase/src/hvc/trng.rs b/libs/libvmbase/src/arch/aarch64/hvc/trng.rs
similarity index 97%
rename from libs/libvmbase/src/hvc/trng.rs
rename to libs/libvmbase/src/arch/aarch64/hvc/trng.rs
index efb86f6..26a515e 100644
--- a/libs/libvmbase/src/hvc/trng.rs
+++ b/libs/libvmbase/src/arch/aarch64/hvc/trng.rs
@@ -54,12 +54,15 @@
     }
 }
 
+/// Local result alias
 pub type Result<T> = result::Result<T, Error>;
 
 /// A version of the SMCCC TRNG interface.
 #[derive(Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
 pub struct Version {
+    /// Version majon number
     pub major: u16,
+    /// Version minor number
     pub minor: u16,
 }
 
diff --git a/libs/libvmbase/src/layout/crosvm.rs b/libs/libvmbase/src/arch/aarch64/layout.rs
similarity index 62%
rename from libs/libvmbase/src/layout/crosvm.rs
rename to libs/libvmbase/src/arch/aarch64/layout.rs
index 39a8147..fe72919 100644
--- a/libs/libvmbase/src/layout/crosvm.rs
+++ b/libs/libvmbase/src/arch/aarch64/layout.rs
@@ -16,7 +16,9 @@
 //!
 //! https://crosvm.dev/book/appendix/memory_layout.html#common-layout
 
+use crate::memory::page_4kb_of;
 use core::ops::Range;
+use static_assertions::const_assert_eq;
 
 /// The start address of MMIO space.
 pub const MMIO_START: usize = 0x0;
@@ -33,3 +35,18 @@
 
 /// Size of the FDT region as defined by crosvm, both in kernel and BIOS modes.
 pub const FDT_MAX_SIZE: usize = 2 << 20;
+
+/// 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(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]));
diff --git a/libs/libvmbase/src/linker.rs b/libs/libvmbase/src/arch/aarch64/linker.rs
similarity index 100%
rename from libs/libvmbase/src/linker.rs
rename to libs/libvmbase/src/arch/aarch64/linker.rs
diff --git a/libs/libvmbase/src/memory/page_table.rs b/libs/libvmbase/src/arch/aarch64/page_table.rs
similarity index 98%
rename from libs/libvmbase/src/memory/page_table.rs
rename to libs/libvmbase/src/arch/aarch64/page_table.rs
index 62b52ae..5f3ed0a 100644
--- a/libs/libvmbase/src/memory/page_table.rs
+++ b/libs/libvmbase/src/arch/aarch64/page_table.rs
@@ -23,7 +23,7 @@
 use core::result;
 
 /// Software bit used to indicate a device that should be lazily mapped.
-pub(super) const MMIO_LAZY_MAP_FLAG: Attributes = Attributes::SWFLAG_0;
+pub const MMIO_LAZY_MAP_FLAG: Attributes = Attributes::SWFLAG_0;
 
 /// We assume that MAIR_EL1.Attr0 = "Device-nGnRE memory" (0b0000_0100)
 const DEVICE_NGNRE: Attributes = Attributes::ATTRIBUTE_INDEX_0;
diff --git a/libs/libvmbase/src/arch/aarch64/platform.rs b/libs/libvmbase/src/arch/aarch64/platform.rs
new file mode 100644
index 0000000..6a62f8c
--- /dev/null
+++ b/libs/libvmbase/src/arch/aarch64/platform.rs
@@ -0,0 +1,137 @@
+// 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.
+
+//! Definition of platform
+
+use crate::{
+    arch::aarch64::{
+        layout::{UART_ADDRESSES, UART_PAGE_ADDR},
+        uart::Uart,
+    },
+    memory::{SIZE_16KB, SIZE_4KB},
+};
+use smccc::{
+    psci::{system_off, system_reset},
+    Hvc,
+};
+use spin::{mutex::SpinMutex, once::Once};
+use static_assertions::const_assert_eq;
+
+// Arbitrary limit on the number of consoles that can be registered.
+//
+// Matches the UART count in crosvm.
+const MAX_CONSOLES: usize = 4;
+
+static CONSOLES: [Once<SpinMutex<Uart>>; MAX_CONSOLES] =
+    [Once::new(), Once::new(), Once::new(), Once::new()];
+static ADDRESSES: [Once<usize>; MAX_CONSOLES] =
+    [Once::new(), Once::new(), Once::new(), Once::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.
+///
+/// # Safety
+///
+/// This must be called before using the `print!` and `println!` macros.
+/// The only safe place to execute this function is in rust initialization code.
+///
+/// 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_all_uart(base_addresses: &[usize]) {
+    for (i, &base_address) in base_addresses.iter().enumerate() {
+        // Remember the valid address, for emergency console accesses.
+        ADDRESSES[i].call_once(|| base_address);
+
+        // Initialize the console driver, for normal console accesses.
+        assert!(!CONSOLES[i].is_completed(), "console::init() called more than once");
+        // SAFETY: The caller promised that base_address is the base of a mapped UART with no
+        // aliases.
+        CONSOLES[i].call_once(|| SpinMutex::new(unsafe { Uart::new(base_address) }));
+    }
+}
+
+/// Initialize console by mapping MMIO memory
+pub fn map_uarts_mmio() -> Result<(), hypervisor_backends::Error> {
+    if let Some(mmio_guard) = hypervisor_backends::get_mmio_guard() {
+        mmio_guard.enroll()?;
+
+        // TODO(ptosi): Use MmioSharer::share() to properly track this MMIO_GUARD_MAP.
+        //
+        // The following call shares the UART but also anything else present in 0..granule.
+        //
+        // For 4KiB, that's only the UARTs. For 16KiB, it also covers the RTC and watchdog but, as
+        // neither is used by vmbase clients (and as both are outside of the UART page), they
+        // will never have valid stage-1 mappings to those devices. As a result, this
+        // MMIO_GUARD_MAP isn't affected by the granule size in any visible way. Larger granule
+        // sizes will need to be checked separately, if needed.
+        assert!({
+            let granule = mmio_guard.granule()?;
+            granule == SIZE_4KB || granule == SIZE_16KB
+        });
+        // Validate the assumption above by ensuring that the UART is not moved to another page:
+        const_assert_eq!(UART_PAGE_ADDR, 0);
+        mmio_guard.map(UART_PAGE_ADDR)?;
+    }
+    Ok(())
+}
+
+/// Initialize platform specific device drivers. If this function fails the reboot is issued.
+pub fn init_console() {
+    if map_uarts_mmio().is_err() {
+        // UART mapping failed platform can't provide any output.
+        // Reboot to prevent printing any message.
+        reboot()
+    }
+    // SAFETY: UART_PAGE is mapped at stage-1 (see entry.S) and was just MMIO-guarded.
+    unsafe { init_all_uart(&UART_ADDRESSES) };
+}
+
+/// Return platform uart with specific index
+///
+/// Panics if console was not initialized by calling [`init`] first.
+pub fn uart(id: usize) -> &'static spin::mutex::SpinMutex<Uart> {
+    return CONSOLES[id].get().unwrap();
+}
+
+/// Reinitializes the emergency UART driver and returns 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_uart() -> Uart {
+    // SAFETY: Initialization of UART using dedicated const address.
+    unsafe { Uart::new(UART_ADDRESSES[DEFAULT_EMERGENCY_CONSOLE_INDEX]) }
+}
+
+/// Makes a `PSCI_SYSTEM_OFF` call to shutdown the VM.
+///
+/// Panics if it returns an error.
+pub fn shutdown() -> ! {
+    system_off::<Hvc>().unwrap();
+    #[allow(clippy::empty_loop)]
+    loop {}
+}
+
+/// Makes a `PSCI_SYSTEM_RESET` call to shutdown the VM abnormally.
+///
+/// Panics if it returns an error.
+pub fn reboot() -> ! {
+    system_reset::<Hvc>().unwrap();
+    #[allow(clippy::empty_loop)]
+    loop {}
+}
diff --git a/libs/libvmbase/src/arch/aarch64/rand.rs b/libs/libvmbase/src/arch/aarch64/rand.rs
new file mode 100644
index 0000000..4fd1905
--- /dev/null
+++ b/libs/libvmbase/src/arch/aarch64/rand.rs
@@ -0,0 +1,131 @@
+// Copyright 2025, 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.
+
+//! Random number generator implementation for aarch64 platforms using TRNG
+
+use crate::arch::aarch64::hvc;
+use crate::rand::{Entropy, Error, Result};
+use core::fmt;
+use core::mem::size_of;
+use smccc::{self, Hvc};
+use zerocopy::IntoBytes as _;
+
+/// Error type for rand operations.
+pub enum PlatformError {
+    /// Error during architectural SMCCC call.
+    Smccc(smccc::arch::Error),
+    /// Error during SMCCC TRNG call.
+    Trng(hvc::trng::Error),
+    /// Unsupported SMCCC version.
+    UnsupportedSmcccVersion(smccc::arch::Version),
+    /// Unsupported SMCCC TRNG version.
+    UnsupportedTrngVersion(hvc::trng::Version),
+}
+
+impl From<smccc::arch::Error> for Error {
+    fn from(e: smccc::arch::Error) -> Self {
+        Self::Platform(PlatformError::Smccc(e))
+    }
+}
+
+impl From<hvc::trng::Error> for Error {
+    fn from(e: hvc::trng::Error) -> Self {
+        Self::Platform(PlatformError::Trng(e))
+    }
+}
+
+impl fmt::Display for PlatformError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            Self::Smccc(e) => write!(f, "Architectural SMCCC error: {e}"),
+            Self::Trng(e) => write!(f, "SMCCC TRNG error: {e}"),
+            Self::UnsupportedSmcccVersion(v) => write!(f, "Unsupported SMCCC version {v}"),
+            Self::UnsupportedTrngVersion(v) => write!(f, "Unsupported SMCCC TRNG version {v}"),
+        }
+    }
+}
+
+impl fmt::Debug for PlatformError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{self}")
+    }
+}
+
+pub(crate) const MAX_BYTES_PER_CALL: usize = size_of::<u64>() * 3;
+
+/// Configure the source of entropy.
+pub(crate) fn init() -> Result<()> {
+    // SMCCC TRNG requires SMCCC v1.1.
+    match smccc::arch::version::<Hvc>()? {
+        smccc::arch::Version { major: 1, minor } if minor >= 1 => (),
+        version => return Err(PlatformError::UnsupportedSmcccVersion(version).into()),
+    }
+
+    // TRNG_RND requires SMCCC TRNG v1.0.
+    match hvc::trng_version()? {
+        hvc::trng::Version { major: 1, minor: _ } => (),
+        version => return Err(PlatformError::UnsupportedTrngVersion(version).into()),
+    }
+
+    // TRNG_RND64 doesn't define any special capabilities so ignore the successful result.
+    let _ = hvc::trng_features(hvc::ARM_SMCCC_TRNG_RND64).map_err(|e| {
+        if e == hvc::trng::Error::NotSupported {
+            // SMCCC TRNG is currently our only source of entropy.
+            Error::NoEntropySource
+        } else {
+            e.into()
+        }
+    })?;
+
+    Ok(())
+}
+
+/// Returns an array where the first `n_bytes` bytes hold entropy.
+///
+/// The rest of the array should be ignored.
+pub(crate) fn platform_entropy(n_bytes: usize) -> Result<Entropy> {
+    loop {
+        if let Some(entropy) = rnd64(n_bytes)? {
+            return Ok(entropy);
+        }
+    }
+}
+
+/// Returns an array where the first `n_bytes` bytes hold entropy, if available.
+///
+/// The rest of the array should be ignored.
+fn rnd64(n_bytes: usize) -> Result<Option<Entropy>> {
+    let bits = usize::try_from(u8::BITS).unwrap();
+    let result = hvc::trng_rnd64((n_bytes * bits).try_into().unwrap());
+    let entropy = if matches!(result, Err(hvc::trng::Error::NoEntropy)) {
+        None
+    } else {
+        let r = result?;
+        // From the SMCCC TRNG:
+        //
+        //     A MAX_BITS-bits wide value (Entropy) is returned across X1 to X3.
+        //     The requested conditioned entropy is returned in Entropy[N-1:0].
+        //
+        //             X1     Entropy[191:128]
+        //             X2     Entropy[127:64]
+        //             X3     Entropy[63:0]
+        //
+        //     The bits in Entropy[MAX_BITS-1:N] are 0.
+        let reordered = [r[2].to_le(), r[1].to_le(), r[0].to_le()];
+
+        Some(reordered.as_bytes().try_into().unwrap())
+    };
+
+    Ok(entropy)
+}
diff --git a/libs/libvmbase/src/arch/aarch64/uart.rs b/libs/libvmbase/src/arch/aarch64/uart.rs
new file mode 100644
index 0000000..2ef7d7e
--- /dev/null
+++ b/libs/libvmbase/src/arch/aarch64/uart.rs
@@ -0,0 +1,66 @@
+// Copyright 2025, 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.
+
+//! Uart driver with backend for aarch64 using MMIO
+
+use crate::arch::write_volatile_u8;
+use crate::uart::UartBackend;
+
+/// Alias for default Uart for aarch64 backend with [`MmioBackend`]
+pub type Uart = crate::uart::Uart<MmioBackend>;
+
+/// Backend for [`crate::uart::Uart`] that uses [`crate::arch::write_volatile_u8`] for writing to
+/// hardware registers.
+pub struct MmioBackend {
+    base_address: *mut u8,
+}
+
+impl MmioBackend {
+    /// Constructs a new instance of the UART driver backend for a device at the given base address.
+    ///
+    /// # Safety
+    ///
+    /// The given base address must point to the 8 MMIO control registers of an appropriate UART
+    /// device, which must be mapped into the address space of the process as device memory and not
+    /// have any other aliases.
+    pub unsafe fn new(base_address: usize) -> Self {
+        Self { base_address: base_address as *mut u8 }
+    }
+}
+
+impl UartBackend for MmioBackend {
+    fn write_register_u8(&self, offset: usize, byte: u8) {
+        // SAFETY: We know that the base address points to the control registers of a UART device
+        // which is appropriately mapped.
+        unsafe { write_volatile_u8(self.base_address.add(offset), byte) }
+    }
+}
+
+impl Uart {
+    /// Constructs a new instance of the UART driver for a device at the given base address.
+    ///
+    /// # Safety
+    ///
+    /// The given base address must point to the 8 MMIO control registers of an appropriate UART
+    /// device, which must be mapped into the address space of the process as device memory and not
+    /// have any other aliases.
+    pub unsafe fn new(base_address: usize) -> Self {
+        // SAFETY: Delegated to caller
+        unsafe { Self::create(MmioBackend::new(base_address)) }
+    }
+}
+
+// SAFETY: `MmioBackend` just contains a pointer to device memory, which can be accessed from any
+// context.
+unsafe impl Send for MmioBackend {}
diff --git a/libs/libvmbase/src/console.rs b/libs/libvmbase/src/console.rs
index 7b01bb6..6d9a4fe 100644
--- a/libs/libvmbase/src/console.rs
+++ b/libs/libvmbase/src/console.rs
@@ -14,67 +14,25 @@
 
 //! Console driver for 8250 UART.
 
-use crate::uart::Uart;
+use crate::arch::platform;
 use core::fmt::{write, Arguments, Write};
-use spin::{mutex::SpinMutex, Once};
-
-// Arbitrary limit on the number of consoles that can be registered.
-//
-// Matches the UART count in crosvm.
-const MAX_CONSOLES: usize = 4;
-
-static CONSOLES: [Once<SpinMutex<Uart>>; MAX_CONSOLES] =
-    [Once::new(), Once::new(), Once::new(), Once::new()];
-static ADDRESSES: [Once<usize>; MAX_CONSOLES] =
-    [Once::new(), Once::new(), Once::new(), Once::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 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].call_once(|| base_address);
-
-        // Initialize the console driver, for normal console accesses.
-        assert!(!CONSOLES[i].is_completed(), "console::init() called more than once");
-        // SAFETY: The caller promised that base_address is the base of a mapped UART with no
-        // aliases.
-        CONSOLES[i].call_once(|| SpinMutex::new(unsafe { Uart::new(base_address) }));
-    }
-}
 
 /// 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 uart = &mut *CONSOLES[n].get().unwrap().lock();
-
+    let uart = &mut *platform::uart(n).lock();
     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.
+/// Reinitializes the emergency 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(n: usize, format_args: Arguments) {
-    let Some(addr) = ADDRESSES[n].get() else { return };
-
-    // SAFETY: addr contains the base of a mapped UART, passed in init().
-    let mut uart = unsafe { Uart::new(*addr) };
-
+pub fn ewriteln(format_args: Arguments) {
+    let mut uart = platform::emergency_uart();
     let _ = write(&mut uart, format_args);
     let _ = uart.write_str("\n");
 }
@@ -98,7 +56,7 @@
 /// use `eprintln!` instead.
 macro_rules! println {
     ($($arg:tt)*) => ({
-        $crate::console::console_writeln!($crate::console::DEFAULT_CONSOLE_INDEX, $($arg)*)
+        $crate::console::console_writeln!($crate::arch::platform::DEFAULT_CONSOLE_INDEX, $($arg)*)
     })
 }
 
@@ -111,6 +69,6 @@
 #[macro_export]
 macro_rules! eprintln {
     ($($arg:tt)*) => ({
-        $crate::console::ewriteln($crate::console::DEFAULT_EMERGENCY_CONSOLE_INDEX, format_args!($($arg)*))
+        $crate::console::ewriteln(format_args!($($arg)*))
     })
 }
diff --git a/libs/libvmbase/src/entry.rs b/libs/libvmbase/src/entry.rs
index b681aea..5c74753 100644
--- a/libs/libvmbase/src/entry.rs
+++ b/libs/libvmbase/src/entry.rs
@@ -15,54 +15,20 @@
 //! Rust entry point.
 
 use crate::{
-    bionic, console, heap,
-    layout::{UART_ADDRESSES, UART_PAGE_ADDR},
-    logger,
-    memory::{switch_to_dynamic_page_tables, PAGE_SIZE, SIZE_16KB, SIZE_4KB},
-    power::{reboot, shutdown},
+    arch::platform,
+    bionic, heap, logger,
+    memory::{switch_to_dynamic_page_tables, PAGE_SIZE, SIZE_4KB},
+    power::shutdown,
     rand,
 };
 use core::mem::size_of;
-use hypervisor_backends::{get_mmio_guard, Error};
-use static_assertions::const_assert_eq;
-
-fn try_console_init() -> Result<(), Error> {
-    if let Some(mmio_guard) = get_mmio_guard() {
-        mmio_guard.enroll()?;
-
-        // TODO(ptosi): Use MmioSharer::share() to properly track this MMIO_GUARD_MAP.
-        //
-        // The following call shares the UART but also anything else present in 0..granule.
-        //
-        // For 4KiB, that's only the UARTs. For 16KiB, it also covers the RTC and watchdog but, as
-        // neither is used by vmbase clients (and as both are outside of the UART page), they
-        // will never have valid stage-1 mappings to those devices. As a result, this
-        // MMIO_GUARD_MAP isn't affected by the granule size in any visible way. Larger granule
-        // sizes will need to be checked separately, if needed.
-        assert!({
-            let granule = mmio_guard.granule()?;
-            granule == SIZE_4KB || granule == SIZE_16KB
-        });
-        // Validate the assumption above by ensuring that the UART is not moved to another page:
-        const_assert_eq!(UART_PAGE_ADDR, 0);
-        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(())
-}
 
 /// This is the entry point to the Rust code, called from the binary entry point in `entry.S`.
 #[no_mangle]
 extern "C" fn rust_entry(x0: u64, x1: u64, x2: u64, x3: u64) -> ! {
     heap::init();
-
-    if try_console_init().is_err() {
-        // Don't panic (or log) here to avoid accessing the console.
-        reboot()
-    }
+    // Initialize platform drivers
+    platform::init_console();
 
     logger::init().expect("Failed to initialize the logger");
     // We initialize the logger to Off (like the log crate) and clients should log::set_max_level.
@@ -72,7 +38,7 @@
     // We keep a null byte at the top of the stack guard to act as a string terminator.
     let random_guard = &mut stack_guard[..(SIZE_OF_STACK_GUARD - 1)];
 
-    if let Err(e) = rand::init() {
+    if let Err(e) = crate::arch::rand::init() {
         panic!("Failed to initialize a source of entropy: {e}");
     }
 
diff --git a/libs/libvmbase/src/layout.rs b/libs/libvmbase/src/layout.rs
index 4c45eb2..8bc2319 100644
--- a/libs/libvmbase/src/layout.rs
+++ b/libs/libvmbase/src/layout.rs
@@ -16,34 +16,24 @@
 
 #![allow(unused_unsafe)]
 
-pub mod crosvm;
-
-use crate::linker::__stack_chk_guard;
-use crate::memory::{max_stack_size, page_4kb_of, PAGE_SIZE};
-use aarch64_paging::paging::VirtualAddress;
+#[cfg(target_arch = "aarch64")]
+use crate::arch::aarch64::linker::__stack_chk_guard;
+use crate::arch::VirtualAddress;
+use crate::memory::{max_stack_size, PAGE_SIZE};
 use core::ops::Range;
-use static_assertions::const_assert_eq;
+
+#[cfg(target_arch = "aarch64")]
+pub use crate::arch::aarch64::layout as crosvm;
 
 /// 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(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]
 macro_rules! linker_addr {
     ($symbol:ident) => {{
-        let addr = (&raw const $crate::linker::$symbol) as usize;
+        #[cfg(target_arch = "aarch64")]
+        let addr = (&raw const $crate::arch::aarch64::linker::$symbol) as usize;
         VirtualAddress(addr)
     }};
 }
@@ -54,7 +44,6 @@
     ($begin:ident,$end:ident) => {{
         let start = linker_addr!($begin);
         let end = linker_addr!($end);
-
         start..end
     }};
 }
@@ -110,8 +99,9 @@
 }
 
 /// Range of the page at UART_PAGE_ADDR of PAGE_SIZE.
+#[cfg(target_arch = "aarch64")]
 pub fn console_uart_page() -> Range<VirtualAddress> {
-    VirtualAddress(UART_PAGE_ADDR)..VirtualAddress(UART_PAGE_ADDR + PAGE_SIZE)
+    VirtualAddress(crosvm::UART_PAGE_ADDR)..VirtualAddress(crosvm::UART_PAGE_ADDR + PAGE_SIZE)
 }
 
 /// Read-write data (original).
diff --git a/libs/libvmbase/src/lib.rs b/libs/libvmbase/src/lib.rs
index 431e899..d254038 100644
--- a/libs/libvmbase/src/lib.rs
+++ b/libs/libvmbase/src/lib.rs
@@ -22,12 +22,9 @@
 pub mod bionic;
 pub mod console;
 mod entry;
-pub mod exceptions;
 pub mod fdt;
 pub mod heap;
-mod hvc;
 pub mod layout;
-pub mod linker;
 pub mod logger;
 pub mod memory;
 pub mod power;
diff --git a/libs/libvmbase/src/memory.rs b/libs/libvmbase/src/memory.rs
index 9153706..afd70aa 100644
--- a/libs/libvmbase/src/memory.rs
+++ b/libs/libvmbase/src/memory.rs
@@ -14,22 +14,23 @@
 
 //! Memory management.
 
-mod dbm;
 mod error;
-mod page_table;
 mod shared;
 mod stack;
 mod tracker;
 mod util;
 
 pub use error::MemoryTrackerError;
-pub use page_table::PageTable;
 pub use shared::MemoryRange;
 pub use tracker::{
     deactivate_dynamic_page_tables, init_shared_pool, map_data, map_data_noflush, map_device,
     map_image_footer, map_rodata, map_rodata_outside_main_memory, resize_available_memory,
     unshare_all_memory, unshare_all_mmio_except_uart, unshare_uart,
 };
+
+#[cfg(target_arch = "aarch64")]
+pub use crate::arch::aarch64::page_table::PageTable;
+
 pub use util::{
     flush, flushed_zeroize, page_4kb_of, PAGE_SIZE, SIZE_128KB, SIZE_16KB, SIZE_2MB, SIZE_4KB,
     SIZE_4MB, SIZE_64KB,
diff --git a/libs/libvmbase/src/memory/shared.rs b/libs/libvmbase/src/memory/shared.rs
index 7e5e7e9..ce57793 100644
--- a/libs/libvmbase/src/memory/shared.rs
+++ b/libs/libvmbase/src/memory/shared.rs
@@ -16,9 +16,10 @@
 
 use super::error::MemoryTrackerError;
 use super::util::virt_to_phys;
+use crate::arch::VirtualAddress;
 use crate::layout;
 use crate::util::unchecked_align_down;
-use aarch64_paging::paging::{MemoryRegion as VaRange, VirtualAddress, PAGE_SIZE};
+use aarch64_paging::paging::{MemoryRegion as VaRange, PAGE_SIZE};
 use alloc::alloc::{alloc_zeroed, dealloc, handle_alloc_error};
 use alloc::collections::BTreeSet;
 use alloc::vec::Vec;
@@ -74,7 +75,7 @@
         let base = unchecked_align_down(phys, self.granule);
 
         // TODO(ptosi): Share the UART using this method and remove the hardcoded check.
-        if self.frames.contains(&base) || base == layout::UART_PAGE_ADDR {
+        if self.frames.contains(&base) || base == layout::crosvm::UART_PAGE_ADDR {
             return Err(MemoryTrackerError::DuplicateMmioShare(base));
         }
 
diff --git a/libs/libvmbase/src/memory/tracker.rs b/libs/libvmbase/src/memory/tracker.rs
index bbff254..cdaae55 100644
--- a/libs/libvmbase/src/memory/tracker.rs
+++ b/libs/libvmbase/src/memory/tracker.rs
@@ -14,15 +14,16 @@
 
 //! Memory management.
 
-use super::dbm::{flush_dirty_range, mark_dirty_block, set_dbm_enabled};
 use super::error::MemoryTrackerError;
-use super::page_table::{PageTable, MMIO_LAZY_MAP_FLAG};
 use super::shared::{SHARED_MEMORY, SHARED_POOL};
+use crate::arch::aarch64::page_table::{PageTable, MMIO_LAZY_MAP_FLAG};
+use crate::arch::dbm::{flush_dirty_range, mark_dirty_block, set_dbm_enabled};
+use crate::arch::VirtualAddress;
 use crate::dsb;
 use crate::layout;
 use crate::memory::shared::{MemoryRange, MemorySharer, MmioSharer};
 use crate::util::RangeExt as _;
-use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion as VaRange, VirtualAddress};
+use aarch64_paging::paging::{Attributes, Descriptor, MemoryRegion as VaRange};
 use alloc::boxed::Box;
 use buddy_system_allocator::LockedFrameAllocator;
 use core::mem::size_of;
@@ -119,7 +120,7 @@
 /// Unshare the UART page, previously shared with the host.
 pub fn unshare_uart() -> Result<()> {
     let Some(mmio_guard) = get_mmio_guard() else { return Ok(()) };
-    Ok(mmio_guard.unmap(layout::UART_PAGE_ADDR)?)
+    Ok(mmio_guard.unmap(layout::crosvm::UART_PAGE_ADDR)?)
 }
 
 /// Map the provided range as normal memory, with R/W permissions.
diff --git a/libs/libvmbase/src/power.rs b/libs/libvmbase/src/power.rs
index 9240acf..e3461c5 100644
--- a/libs/libvmbase/src/power.rs
+++ b/libs/libvmbase/src/power.rs
@@ -13,26 +13,18 @@
 // limitations under the License.
 
 //! Functions for shutting down the VM.
+use crate::arch::platform;
 
-use smccc::{
-    psci::{system_off, system_reset},
-    Hvc,
-};
-
-/// Makes a `PSCI_SYSTEM_OFF` call to shutdown the VM.
+/// Call shutdown VM using platform specific code.
 ///
 /// Panics if it returns an error.
 pub fn shutdown() -> ! {
-    system_off::<Hvc>().unwrap();
-    #[allow(clippy::empty_loop)]
-    loop {}
+    platform::shutdown();
 }
 
-/// Makes a `PSCI_SYSTEM_RESET` call to shutdown the VM abnormally.
+/// Call reboot VM using platform specific code.
 ///
 /// Panics if it returns an error.
 pub fn reboot() -> ! {
-    system_reset::<Hvc>().unwrap();
-    #[allow(clippy::empty_loop)]
-    loop {}
+    platform::reboot();
 }
diff --git a/libs/libvmbase/src/rand.rs b/libs/libvmbase/src/rand.rs
index 16c7b6a..e4b8c63 100644
--- a/libs/libvmbase/src/rand.rs
+++ b/libs/libvmbase/src/rand.rs
@@ -14,37 +14,23 @@
 
 //! Functions and drivers for obtaining true entropy.
 
-use crate::hvc;
+use crate::arch::rand::{platform_entropy, PlatformError, MAX_BYTES_PER_CALL};
 use core::fmt;
-use core::mem::size_of;
-use smccc::{self, Hvc};
-use zerocopy::IntoBytes as _;
 
-type Entropy = [u8; size_of::<u64>() * 3];
+pub(crate) type Entropy = [u8; MAX_BYTES_PER_CALL];
 
 /// Error type for rand operations.
 pub enum Error {
     /// No source of entropy found.
     NoEntropySource,
-    /// Error during architectural SMCCC call.
-    Smccc(smccc::arch::Error),
-    /// Error during SMCCC TRNG call.
-    Trng(hvc::trng::Error),
-    /// Unsupported SMCCC version.
-    UnsupportedSmcccVersion(smccc::arch::Version),
-    /// Unsupported SMCCC TRNG version.
-    UnsupportedTrngVersion(hvc::trng::Version),
+
+    /// Platform specific error
+    Platform(PlatformError),
 }
 
-impl From<smccc::arch::Error> for Error {
-    fn from(e: smccc::arch::Error) -> Self {
-        Self::Smccc(e)
-    }
-}
-
-impl From<hvc::trng::Error> for Error {
-    fn from(e: hvc::trng::Error) -> Self {
-        Self::Trng(e)
+impl From<PlatformError> for Error {
+    fn from(e: PlatformError) -> Self {
+        Error::Platform(e)
     }
 }
 
@@ -55,10 +41,7 @@
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
             Self::NoEntropySource => write!(f, "No source of entropy available"),
-            Self::Smccc(e) => write!(f, "Architectural SMCCC error: {e}"),
-            Self::Trng(e) => write!(f, "SMCCC TRNG error: {e}"),
-            Self::UnsupportedSmcccVersion(v) => write!(f, "Unsupported SMCCC version {v}"),
-            Self::UnsupportedTrngVersion(v) => write!(f, "Unsupported SMCCC TRNG version {v}"),
+            Self::Platform(e) => write!(f, "Platform error: {e}"),
         }
     }
 }
@@ -69,84 +52,16 @@
     }
 }
 
-/// Configure the source of entropy.
-pub(crate) fn init() -> Result<()> {
-    // SMCCC TRNG requires SMCCC v1.1.
-    match smccc::arch::version::<Hvc>()? {
-        smccc::arch::Version { major: 1, minor } if minor >= 1 => (),
-        version => return Err(Error::UnsupportedSmcccVersion(version)),
-    }
-
-    // TRNG_RND requires SMCCC TRNG v1.0.
-    match hvc::trng_version()? {
-        hvc::trng::Version { major: 1, minor: _ } => (),
-        version => return Err(Error::UnsupportedTrngVersion(version)),
-    }
-
-    // TRNG_RND64 doesn't define any special capabilities so ignore the successful result.
-    let _ = hvc::trng_features(hvc::ARM_SMCCC_TRNG_RND64).map_err(|e| {
-        if e == hvc::trng::Error::NotSupported {
-            // SMCCC TRNG is currently our only source of entropy.
-            Error::NoEntropySource
-        } else {
-            e.into()
-        }
-    })?;
-
-    Ok(())
-}
-
 /// Fills a slice of bytes with true entropy.
 pub fn fill_with_entropy(s: &mut [u8]) -> Result<()> {
-    const MAX_BYTES_PER_CALL: usize = size_of::<Entropy>();
-
     for chunk in s.chunks_mut(MAX_BYTES_PER_CALL) {
-        let entropy = repeat_trng_rnd(chunk.len())?;
+        let entropy = platform_entropy(chunk.len())?;
         chunk.clone_from_slice(&entropy[..chunk.len()]);
     }
 
     Ok(())
 }
 
-/// Returns an array where the first `n_bytes` bytes hold entropy.
-///
-/// The rest of the array should be ignored.
-fn repeat_trng_rnd(n_bytes: usize) -> Result<Entropy> {
-    loop {
-        if let Some(entropy) = rnd64(n_bytes)? {
-            return Ok(entropy);
-        }
-    }
-}
-
-/// Returns an array where the first `n_bytes` bytes hold entropy, if available.
-///
-/// The rest of the array should be ignored.
-fn rnd64(n_bytes: usize) -> Result<Option<Entropy>> {
-    let bits = usize::try_from(u8::BITS).unwrap();
-    let result = hvc::trng_rnd64((n_bytes * bits).try_into().unwrap());
-    let entropy = if matches!(result, Err(hvc::trng::Error::NoEntropy)) {
-        None
-    } else {
-        let r = result?;
-        // From the SMCCC TRNG:
-        //
-        //     A MAX_BITS-bits wide value (Entropy) is returned across X1 to X3.
-        //     The requested conditioned entropy is returned in Entropy[N-1:0].
-        //
-        //             X1     Entropy[191:128]
-        //             X2     Entropy[127:64]
-        //             X3     Entropy[63:0]
-        //
-        //     The bits in Entropy[MAX_BITS-1:N] are 0.
-        let reordered = [r[2].to_le(), r[1].to_le(), r[0].to_le()];
-
-        Some(reordered.as_bytes().try_into().unwrap())
-    };
-
-    Ok(entropy)
-}
-
 /// Generate an array of fixed-size initialized with true-random bytes.
 pub fn random_array<const N: usize>() -> Result<[u8; N]> {
     let mut arr = [0; N];
diff --git a/libs/libvmbase/src/uart.rs b/libs/libvmbase/src/uart.rs
index 427499b..857a22e 100644
--- a/libs/libvmbase/src/uart.rs
+++ b/libs/libvmbase/src/uart.rs
@@ -15,36 +15,33 @@
 //! Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250
 //! provided by crosvm, and won't work with real hardware.
 
-use crate::arch::write_volatile_u8;
 use core::fmt::{self, Write};
 
+/// The backend for [`Uart`] that abstracts the access to 8250 register map
+pub trait UartBackend {
+    /// Writes a byte value on the offset to the hardware registers.
+    fn write_register_u8(&self, offset: usize, byte: u8);
+}
+
 /// Minimal driver for an 8250 UART. This only implements enough to work with the emulated 8250
 /// provided by crosvm, and won't work with real hardware.
-pub struct Uart {
-    base_address: *mut u8,
+pub struct Uart<Backend: UartBackend> {
+    backend: Backend,
 }
 
-impl Uart {
-    /// Constructs a new instance of the UART driver for a device at the given base address.
-    ///
-    /// # Safety
-    ///
-    /// The given base address must point to the 8 MMIO control registers of an appropriate UART
-    /// device, which must be mapped into the address space of the process as device memory and not
-    /// have any other aliases.
-    pub unsafe fn new(base_address: usize) -> Self {
-        Self { base_address: base_address as *mut u8 }
+impl<Backend: UartBackend> Uart<Backend> {
+    /// Constructs a new instance of the UART driver with given backend.
+    pub(crate) fn create(backend: Backend) -> Self {
+        Self { backend }
     }
 
     /// Writes a single byte to the UART.
     pub fn write_byte(&self, byte: u8) {
-        // SAFETY: We know that the base address points to the control registers of a UART device
-        // which is appropriately mapped.
-        unsafe { write_volatile_u8(self.base_address, byte) }
+        self.backend.write_register_u8(0, byte)
     }
 }
 
-impl Write for Uart {
+impl<Backend: UartBackend> Write for Uart<Backend> {
     fn write_str(&mut self, s: &str) -> fmt::Result {
         for c in s.as_bytes() {
             self.write_byte(*c);
@@ -52,6 +49,3 @@
         Ok(())
     }
 }
-
-// SAFETY: `Uart` just contains a pointer to device memory, which can be accessed from any context.
-unsafe impl Send for Uart {}