Merge "Add config of audio device" into main
diff --git a/README.md b/README.md
index 7560a45..f417b00 100644
--- a/README.md
+++ b/README.md
@@ -32,4 +32,5 @@
* [Debugging](docs/debug)
* [Using custom VM](docs/custom_vm.md)
* [Device assignment](docs/device_assignment.md)
+* [Microdroid vendor modules](docs/microdroid_vendor_modules.md)
* [Huge Pages](docs/hugepages.md)
diff --git a/docs/abl.md b/docs/abl.md
index b08464e..7139d26 100644
--- a/docs/abl.md
+++ b/docs/abl.md
@@ -22,7 +22,9 @@
* DICE chain (also known as BCC Handover)
* DTBO describing [debug policy](debug/README.md#debug-policy) (if available)
* DTBO describing [assignable devices](device_assignment.md) (if available)
-* Reference DT carrying extra information that needs to be passed to the guest VM
+* Reference DT carrying extra information that needs to be passed to the guest VM, e.g.:
+ * Hashtree digest of the
+ [microdroid-vendor.img](microdroid_vendor_modules.md#changes-in-abl).
See [Configuration Data](../pvmfw/README.md#configuration-data) for more detail.
diff --git a/docs/microdroid_vendor_modules.md b/docs/microdroid_vendor_modules.md
new file mode 100644
index 0000000..ef55225
--- /dev/null
+++ b/docs/microdroid_vendor_modules.md
@@ -0,0 +1,178 @@
+# Microdroid vendor modules
+
+Starting with Android V it is possible to start a Microdroid VM with a
+vendor-prodived kernel modules. This feature is part of the bigger
+[device assignment](device_assignmnent.md) effort.
+
+The vendor kernel modules should be packaged inside a `microdroid-vendor.img`
+dm-verity protected partition, inside a Microdroid VM this will be mounted as
+`/vendor` partition.
+
+Currently the following features are supported:
+* Kernel modules;
+* init .rc scripts with basic triggers (e.g. `on early-init`);
+* `ueventd.rc` file;
+* `/vendor/etc/selinux/vendor_file_contexts` file.
+
+
+Additionallity, starting with android15-6.6 it is possible to start a Microdroid
+VM with GKI as guest kernel. This is **required** when launching a Microdroid VM with
+vendor provided kernel modules.
+
+**Note:** in Android V, the 'Microdroid vendor modules' is considered an experimental
+feature to provide our partners a reference implementation that they can start
+integrating with to flesh out missing pieces.
+We **do not recommened** launching user-facing features that depend on using
+vendor modules in a pVM.
+
+
+## Integrating into a product {#build-system-integration}
+
+You can define microdroid vendor partition using `android_filesystem` soong
+module, here is an example:
+
+```
+android_filesystem {
+ name: "microdroid_vendor_example_image",
+ partition_name: "microdroid-vendor",
+ type: "ext4",
+ file_contexts: "file_contexts",
+ use_avb: true,
+ avb_private_key: ":microdroid_vendor_example_sign_key",
+ mount_point: "vendor",
+ deps: [
+ "microdroid_vendor_example_ueventd",
+ "microdroid_vendor_example_file_contexts",
+ "microdroid_vendor_example_kernel_modules",
+ "microdroid_vendor_example.rc",
+ ],
+}
+
+prebuilt_etc {
+ name: "microdroid_vendor_example",
+ src: ":microdroid_vendor_example_image",
+ relative_install_path: "avf/microdroid",
+ filename: "microdroid_vendor.img",
+ vendor: true,
+}
+```
+
+In order to integrate the microdroid vendor partition into a product, add the
+following lines to the corresponding device makefile:
+
+```
+PRODUCT_PACKAGES += microdroid_vendor_example
+MICRODROID_VENDOR_IMAGE_MODULE := microdroid_vendor_example
+```
+
+**Note**: it is important that the microdroid-vendor.img is installed into
+`/vendor/etc/avf/microdroid/microdroid_vendor.img` on the device.
+
+
+## Launching a Microdroid VM wirth vendor partition
+
+### Non-protected VMs
+
+You can launch a non-protected Microdroid VM with vendor partition by adding the
+`--vendor` argument to the `/apex/com.android.virt/bin/vm run-app` or
+`/apex/com.android.virt/bin/vm run-microdroid` CLI commands, e.g.:
+
+```
+adb shell /apex/com.android.virt/bin/vm/run-microdroid \
+ --debug full \
+ --vendor /vendor/etc/avf/microdroid/microdroid_vendor.img
+```
+
+On the Android host side, the `virtmgr` will append the
+`vendor_hashtree_descriptor_root_digest` property to the `/avf` node of the
+guest device tree overlay. Value of this property will contain the hashtree
+digest of the `microdroid_vendor.img` provided via the `--vendor` argument.
+
+Inside the Microdroid guest VM, the `first_stage_init` will use the
+`/proc/device-tree/avf/vendor_hashtree_descriptor_root_digest` to create a
+`dm-verity` device on top of the `/dev/block/by-name/microdroid-vendor` block
+device. The `/vendor` partition will be mounted on top of the created
+`dm-verity` device.
+
+TODO(ioffe): create drawings and add them here.
+
+
+### Protected VMs
+
+As of now, only **debuggable** Microdroid pVMs support running with the
+Microdroid vendor partition, e.g.:
+
+```
+adb shell /apex/com.android.virt/bin/vm/run-microdroid \
+ --debug full \
+ --protected \
+ --vendor /vendor/etc/avf/microdroid/microdroid_vendor.img
+```
+
+The execution flow is very similar to the non-protected case above, however
+there is one important addition. The `pvmfw` binary will use the
+[VM reference DT blob](#../pvmfw/README.md#pvmfw-data-v1-2) passed from the
+Android Bootloader (ABL), to validate the guest DT overlay passed from the host.
+
+See [Changes in Android Bootloader](#changes-in-abl) section below for more
+details.
+
+### Reflecting microdroid vendor partition in the guest DICE chain
+
+The microdroid vendor partition will be reflected as a separate
+`Microdroid vendor` node in the Microdroid DICE chain.
+
+TODO(ioffe): drawing of DICE chain here.
+
+This node derivation happens in the `derive_microdroid_vendor_dice_node`, which
+is executed by `first_stage_init`. The binary will write the new DICE chain into
+the `/microdroid_resources/dice_chain.raw` file, which will be then read by
+`microdroid_manager` to derive the final `Microdroid payload` DICE node.
+
+TODO(ioffe): another drawing here.
+
+## Changes in the Android Bootloader {#changes-in-abl}
+
+In order for a Microdroid pVM with the
+`/vendor/etc/avf/microdroid/microdroid_vendor.img` to successfully boot, the
+ABL is required to pass the correct value of the
+`/vendor/etc/avf/microdroid/microdroid_vendor.img` hashtree digest in the
+`vendor_hashtree_descriptor_root_digest` property of `the /avf/reference` node.
+
+The `MICRODROID_VENDOR_IMAGE_MODULE` make variable mentioned in the
+[section above](#build-system-integration) configures build system to inject
+the value of the `microdroid-vendor.img` hashtree digest into the
+`com.android.build.microdroid-vendor.root_digest ` property of the footer of
+the host's `vendor.img`.
+
+The Android Bootloader can read that property when construction the
+[VM reference DT blob](#../pvmfw/README.md#pvmfw-data-v1-2) passed to pvmfw.
+
+## GKI as Microdroid guest kernel
+
+In order to enable running Microdroid with GKI as guest kernel, specify the
+`PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION ` variable in a product makefile:
+
+```
+PRODUCT_AVF_MICRODROID_GUEST_GKI_VERSION := android15_66
+```
+
+Note: currently this will alter the content of the `com.android.virt` APEX by
+installing the corresponding GKI image into it. In the future, the GKI image
+will be installed on the `/system_ext` partition.
+
+The following changes to the `gki_defconfig` were made to support running as
+guest kernel:
+
+```
+CONFIG_VIRTIO_VSOCKETS=m
+CONFIG_VIRTIO_BLK=m
+CONFIG_OPEN_DICE=m
+CONFIG_VCPU_STALL_DETECTOR=m
+CONFIG_VIRTIO_CONSOLE=m
+CONFIG_HW_RANDOM_CCTRNG=m
+CONFIG_VIRTIO_PCI=m
+CONFIG_VIRTIO_BALLOON=m
+CONFIG_DMA_RESTRICTED_POOL=y
+```
+
diff --git a/pvmfw/README.md b/pvmfw/README.md
index 053e4f7..7a03f0b 100644
--- a/pvmfw/README.md
+++ b/pvmfw/README.md
@@ -187,18 +187,26 @@
of the array. The header uses the endianness of the virtual machine.
The header format itself is agnostic of the internal format of the individual
-blos it refers to. In version 1.0, it describes two blobs:
+blos it refers to.
+
+##### Version 1.0 {#pvmfw-data-v1-0}
+
+In version 1.0, it describes two blobs:
- entry 0 must point to a valid DICE chain handover (see below)
- entry 1 may point to a [DTBO] to be applied to the pVM device tree. See
[debug policy][debug_policy] for an example.
+##### Version 1.1 {#pvmfw-data-v1-1}
+
In version 1.1, a third blob is added.
- entry 2 may point to a [DTBO] that describes VM DA DTBO for
[device assignment][device_assignment].
pvmfw will provision assigned devices with the VM DTBO.
+#### Version 1.2 {#pvmfw-data-v1-2}
+
In version 1.2, a fourth blob is added.
- entry 3 if present contains the VM reference DT. This defines properties that
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index c6f26ac..8683e69 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -20,7 +20,11 @@
use avb::{DescriptorError, SlotVerifyError};
use avb_bindgen::{AvbFooter, AvbVBMetaImageHeader};
use pvmfw_avb::{verify_payload, Capability, DebugLevel, PvmfwVerifyError, VerifiedBootData};
-use std::{fs, mem::size_of, ptr};
+use std::{
+ fs,
+ mem::{offset_of, size_of},
+ ptr,
+};
use utils::*;
const TEST_IMG_WITH_ONE_HASHDESC_PATH: &str = "test_image_with_one_hashdesc.img";
@@ -243,32 +247,20 @@
fn kernel_footer_with_vbmeta_offset_overwritten_fails_verification() -> Result<()> {
// Arrange.
let mut kernel = load_latest_signed_kernel()?;
- let total_len = kernel.len() as u64;
- let footer = extract_avb_footer(&kernel)?;
- assert!(footer.vbmeta_offset < total_len);
- // TODO: use core::mem::offset_of once stable.
- let footer_addr = ptr::addr_of!(footer) as *const u8;
- let vbmeta_offset_addr = ptr::addr_of!(footer.vbmeta_offset) as *const u8;
- let vbmeta_offset_start =
- // SAFETY:
- // - both raw pointers `vbmeta_offset_addr` and `footer_addr` are not null;
- // - they are both derived from the `footer` object;
- // - the offset is known from the struct definition to be a small positive number of bytes.
- unsafe { vbmeta_offset_addr.offset_from(footer_addr) };
- let footer_start = kernel.len() - size_of::<AvbFooter>();
- let vbmeta_offset_start = footer_start + usize::try_from(vbmeta_offset_start)?;
+ let footer_offset = get_avb_footer_offset(&kernel)?;
+ let vbmeta_offset_offset = footer_offset + offset_of!(AvbFooter, vbmeta_offset);
+ let vbmeta_offset_bytes = vbmeta_offset_offset..(vbmeta_offset_offset + size_of::<u64>());
- let wrong_offsets = [total_len, u64::MAX];
- for &wrong_offset in wrong_offsets.iter() {
+ let test_values = [kernel.len(), usize::MAX];
+ for value in test_values {
+ let value = u64::try_from(value).unwrap();
// Act.
- kernel[vbmeta_offset_start..(vbmeta_offset_start + size_of::<u64>())]
- .copy_from_slice(&wrong_offset.to_be_bytes());
+ kernel[vbmeta_offset_bytes.clone()].copy_from_slice(&value.to_be_bytes());
+ // footer is unaligned; copy vbmeta_offset to local variable
+ let vbmeta_offset = extract_avb_footer(&kernel)?.vbmeta_offset;
+ assert_eq!(vbmeta_offset, value);
// Assert.
- let footer = extract_avb_footer(&kernel)?;
- // footer is unaligned; copy vbmeta_offset to local variable
- let vbmeta_offset = footer.vbmeta_offset;
- assert_eq!(wrong_offset, vbmeta_offset);
assert_payload_verification_with_initrd_fails(
&kernel,
&load_latest_initrd_normal()?,
diff --git a/pvmfw/avb/tests/utils.rs b/pvmfw/avb/tests/utils.rs
index cf37fcf..e989579 100644
--- a/pvmfw/avb/tests/utils.rs
+++ b/pvmfw/avb/tests/utils.rs
@@ -72,8 +72,14 @@
Ok(fs::read(PUBLIC_KEY_RSA4096_PATH)?)
}
+pub fn get_avb_footer_offset(signed_kernel: &[u8]) -> Result<usize> {
+ let offset = signed_kernel.len().checked_sub(size_of::<AvbFooter>());
+
+ offset.ok_or_else(|| anyhow!("Kernel too small to be AVB-signed"))
+}
+
pub fn extract_avb_footer(kernel: &[u8]) -> Result<AvbFooter> {
- let footer_start = kernel.len() - size_of::<AvbFooter>();
+ let footer_start = get_avb_footer_offset(kernel)?;
// SAFETY: The slice is the same size as the struct which only contains simple data types.
let mut footer = unsafe {
transmute::<[u8; size_of::<AvbFooter>()], AvbFooter>(kernel[footer_start..].try_into()?)
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 43822a5..0ff7270 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -30,9 +30,9 @@
use log::LevelFilter;
use vmbase::util::RangeExt as _;
use vmbase::{
- configure_heap, console,
+ configure_heap, console_writeln,
hyp::{get_mem_sharer, get_mmio_guard},
- layout::{self, crosvm},
+ layout::{self, crosvm, UART_PAGE_ADDR},
main,
memory::{min_dcache_line_size, MemoryTracker, MEMORY, SIZE_128KB, SIZE_4KB},
power::reboot,
@@ -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().
@@ -256,7 +275,7 @@
// Call unshare_all_memory here (instead of relying on the dtor) while UART is still mapped.
MEMORY.lock().as_mut().unwrap().unshare_all_memory();
if let Some(mmio_guard) = get_mmio_guard() {
- mmio_guard.unmap(console::BASE_ADDRESS).map_err(|e| {
+ mmio_guard.unmap(UART_PAGE_ADDR).map_err(|e| {
error!("Failed to unshare the UART: {e}");
RebootReason::InternalError
})?;
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 5a3735e..8d12b57 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -51,7 +51,7 @@
page_table.map_code(&layout::text_range().into())?;
page_table.map_rodata(&layout::rodata_range().into())?;
page_table.map_data_dbm(&appended_payload_range().into())?;
- if let Err(e) = page_table.map_device(&layout::console_uart_range().into()) {
+ if let Err(e) = page_table.map_device(&layout::console_uart_page().into()) {
error!("Failed to remap the UART as a dynamic page table entry: {e}");
return Err(e);
}
diff --git a/rialto/src/exceptions.rs b/rialto/src/exceptions.rs
index b806b08..e87e0d3 100644
--- a/rialto/src/exceptions.rs
+++ b/rialto/src/exceptions.rs
@@ -15,7 +15,6 @@
//! Exception handlers.
use vmbase::{
- console::emergency_write_str,
eprintln,
exceptions::{ArmException, Esr, HandleExceptionError},
logger,
@@ -49,45 +48,45 @@
#[no_mangle]
extern "C" fn irq_current() {
- emergency_write_str("irq_current\n");
+ eprintln!("irq_current");
reboot();
}
#[no_mangle]
extern "C" fn fiq_current() {
- emergency_write_str("fiq_current\n");
+ eprintln!("fiq_current");
reboot();
}
#[no_mangle]
extern "C" fn serr_current() {
- emergency_write_str("serr_current\n");
+ eprintln!("serr_current");
print_esr();
reboot();
}
#[no_mangle]
extern "C" fn sync_lower() {
- emergency_write_str("sync_lower\n");
+ eprintln!("sync_lower");
print_esr();
reboot();
}
#[no_mangle]
extern "C" fn irq_lower() {
- emergency_write_str("irq_lower\n");
+ eprintln!("irq_lower");
reboot();
}
#[no_mangle]
extern "C" fn fiq_lower() {
- emergency_write_str("fiq_lower\n");
+ eprintln!("fiq_lower");
reboot();
}
#[no_mangle]
extern "C" fn serr_lower() {
- emergency_write_str("serr_lower\n");
+ eprintln!("serr_lower");
print_esr();
reboot();
}
diff --git a/rialto/src/main.rs b/rialto/src/main.rs
index 864f5e4..701a287 100644
--- a/rialto/src/main.rs
+++ b/rialto/src/main.rs
@@ -48,7 +48,7 @@
configure_heap,
fdt::SwiotlbInfo,
hyp::{get_mem_sharer, get_mmio_guard},
- layout::{self, crosvm},
+ layout::{self, crosvm, UART_PAGE_ADDR},
main,
memory::{MemoryTracker, PageTable, MEMORY, PAGE_SIZE, SIZE_128KB},
power::reboot,
@@ -78,7 +78,7 @@
page_table.map_data(&layout::stack_range(40 * PAGE_SIZE).into())?;
page_table.map_code(&layout::text_range().into())?;
page_table.map_rodata(&layout::rodata_range().into())?;
- page_table.map_device(&layout::console_uart_range().into())?;
+ page_table.map_device(&layout::console_uart_page().into())?;
Ok(page_table)
}
@@ -205,7 +205,7 @@
// No logging after unmapping UART.
if let Some(mmio_guard) = get_mmio_guard() {
- mmio_guard.unmap(vmbase::console::BASE_ADDRESS)?;
+ mmio_guard.unmap(UART_PAGE_ADDR)?;
}
// Unshares all memory and deactivates page table.
drop(MEMORY.lock().take());
diff --git a/tests/ferrochrome/ferrochrome.sh b/tests/ferrochrome/ferrochrome.sh
index 72b5433..210548a 100755
--- a/tests/ferrochrome/ferrochrome.sh
+++ b/tests/ferrochrome/ferrochrome.sh
@@ -27,6 +27,7 @@
FECR_BOOT_COMPLETED_LOG="Have fun and send patches!"
FECR_BOOT_TIMEOUT="300" # 5 minutes (300 seconds)
ACTION_NAME="android.virtualization.VM_LAUNCHER"
+TRY_UNLOCK_MAX=10
fecr_clean_up() {
trap - INT
@@ -132,6 +133,35 @@
adb push ${fecr_script_path}/assets/vm_config.json ${FECR_CONFIG_PATH}
fi
+echo "Ensure screen unlocked"
+
+try_unlock=0
+while [[ "${try_unlock}" -le "${TRY_UNLOCK_MAX}" ]]; do
+ screen_state=$(adb shell dumpsys nfc | sed -n 's/^mScreenState=\(.*\)$/\1/p')
+ case "${screen_state}" in
+ "ON_UNLOCKED")
+ break
+ ;;
+ "ON_LOCKED")
+ # Disclaimer: This can unlock phone only if unlock method is swipe (default after FDR)
+ adb shell input keyevent KEYCODE_MENU
+ ;;
+ "OFF_LOCKED"|"OFF_UNLOCKED")
+ adb shell input keyevent KEYCODE_WAKEUP
+ ;;
+ *)
+ echo "Unknown screen state. Continue to boot, but may fail"
+ break
+ ;;
+ esac
+ sleep 1
+ try_unlock=$((try_unlock+1))
+done
+if [[ "${try_unlock}" -gt "${TRY_UNLOCK_MAX}" ]]; then
+ >&2 echo "Failed to unlock screen. Try again after manual unlock"
+ exit 1
+fi
+
echo "Starting ferrochrome"
adb shell am start-activity -a ${ACTION_NAME} > /dev/null
diff --git a/vmbase/README.md b/vmbase/README.md
index 280d7e1..28d930a 100644
--- a/vmbase/README.md
+++ b/vmbase/README.md
@@ -76,10 +76,10 @@
must use the C ABI, and have the expected names. For example, to log sync exceptions and reboot:
```rust
-use vmbase::{console::emergency_write_str, power::reboot};
+use vmbase::power::reboot;
extern "C" fn sync_exception_current() {
- emergency_write_str("sync_exception_current\n");
+ eprintln!("sync_exception_current");
let mut esr: u64;
unsafe {
@@ -93,14 +93,9 @@
The `println!` macro shouldn't be used in exception handlers, because it relies on a global instance
of the UART driver which might be locked when the exception happens, which would result in deadlock.
-Instead you can use `emergency_write_str` and `eprintln!`, which will re-initialize the UART every
-time to ensure that it can be used. This should still be used with care, as it may interfere with
-whatever the rest of the program is doing with the UART.
-
-Note also that in some cases when the system is in a bad state resulting in the stack not working
-properly, `eprintln!` may hang. `emergency_write_str` may be more reliable as it seems to avoid
-any stack allocation. This is why the example above uses `emergency_write_str` first to ensure that
-at least something is logged, before trying `eprintln!` to print more details.
+Instead you can use `eprintln!`, which will re-initialize the UART every time to ensure that it can
+be used. This should still be used with care, as it may interfere with whatever the rest of the
+program is doing with the UART.
See [example/src/exceptions.rs](examples/src/exceptions.rs) for a complete example.
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index 9fe3a4a..6ea8d60 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -14,8 +14,6 @@
//! Low-level compatibility layer between baremetal Rust and Bionic C functions.
-use crate::console;
-use crate::eprintln;
use crate::rand::fill_with_entropy;
use crate::read_sysreg;
use core::ffi::c_char;
@@ -27,6 +25,8 @@
use core::str;
use cstr::cstr;
+use log::error;
+use log::info;
const EOF: c_int = -1;
const EIO: c_int = 5;
@@ -119,7 +119,7 @@
if let (Ok(prefix), Ok(format)) = (prefix.to_str(), format.to_str()) {
// We don't bother with printf formatting.
- eprintln!("FATAL BIONIC ERROR: {prefix}: \"{format}\" (unformatted)");
+ error!("FATAL BIONIC ERROR: {prefix}: \"{format}\" (unformatted)");
}
}
@@ -130,6 +130,23 @@
Stderr = 0x9d118200,
}
+impl File {
+ fn write_lines(&self, s: &str) {
+ for line in s.split_inclusive('\n') {
+ let (line, ellipsis) = if let Some(stripped) = line.strip_suffix('\n') {
+ (stripped, "")
+ } else {
+ (line, " ...")
+ };
+
+ match self {
+ Self::Stdout => info!("{line}{ellipsis}"),
+ Self::Stderr => error!("{line}{ellipsis}"),
+ }
+ }
+ }
+}
+
impl TryFrom<usize> for File {
type Error = &'static str;
@@ -152,8 +169,8 @@
// SAFETY: Just like libc, we need to assume that `s` is a valid NULL-terminated string.
let c_str = unsafe { CStr::from_ptr(c_str) };
- if let (Ok(s), Ok(_)) = (c_str.to_str(), File::try_from(stream)) {
- console::write_str(s);
+ if let (Ok(s), Ok(f)) = (c_str.to_str(), File::try_from(stream)) {
+ f.write_lines(s);
0
} else {
set_errno(EOF);
@@ -168,8 +185,8 @@
// SAFETY: Just like libc, we need to assume that `ptr` is valid.
let bytes = unsafe { slice::from_raw_parts(ptr as *const u8, length) };
- if let (Ok(s), Ok(_)) = (str::from_utf8(bytes), File::try_from(stream)) {
- console::write_str(s);
+ if let (Ok(s), Ok(f)) = (str::from_utf8(bytes), File::try_from(stream)) {
+ f.write_lines(s);
length
} else {
0
@@ -198,9 +215,9 @@
let error = cstr_error(get_errno()).to_str().unwrap();
if let Some(prefix) = prefix {
- eprintln!("{prefix}: {error}");
+ error!("{prefix}: {error}");
} else {
- eprintln!("{error}");
+ error!("{error}");
}
}
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 bb5ccef..ad633ed 100644
--- a/vmbase/src/entry.rs
+++ b/vmbase/src/entry.rs
@@ -15,8 +15,10 @@
//! Rust entry point.
use crate::{
- bionic, console, heap, hyp, logger,
- memory::{page_4kb_of, SIZE_16KB, SIZE_4KB},
+ bionic, console, heap, hyp,
+ layout::{UART_ADDRESSES, UART_PAGE_ADDR},
+ logger,
+ memory::{SIZE_16KB, SIZE_4KB},
power::{reboot, shutdown},
rand,
};
@@ -24,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()?;
@@ -43,10 +43,13 @@
granule == SIZE_4KB || granule == SIZE_16KB
});
// Validate the assumption above by ensuring that the UART is not moved to another page:
- const_assert_eq!(page_4kb_of(console::BASE_ADDRESS), 0);
- mmio_guard.map(console::BASE_ADDRESS)?;
+ 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(())
}
diff --git a/vmbase/src/exceptions.rs b/vmbase/src/exceptions.rs
index 7833334..11fcd93 100644
--- a/vmbase/src/exceptions.rs
+++ b/vmbase/src/exceptions.rs
@@ -15,15 +15,14 @@
//! Helper functions and structs for exception handlers.
use crate::{
- console, eprintln,
+ eprintln,
+ layout::UART_PAGE_ADDR,
memory::{page_4kb_of, MemoryTrackerError},
read_sysreg,
};
use aarch64_paging::paging::VirtualAddress;
use core::fmt;
-const UART_PAGE: usize = page_4kb_of(console::BASE_ADDRESS);
-
/// Represents an error that can occur while handling an exception.
#[derive(Debug)]
pub enum HandleExceptionError {
@@ -134,6 +133,6 @@
}
fn is_uart_exception(&self) -> bool {
- self.esr == Esr::DataAbortSyncExternalAbort && page_4kb_of(self.far.0) == UART_PAGE
+ self.esr == Esr::DataAbortSyncExternalAbort && page_4kb_of(self.far.0) == UART_PAGE_ADDR
}
}
diff --git a/vmbase/src/layout.rs b/vmbase/src/layout.rs
index f7e8170..5ac435f 100644
--- a/vmbase/src/layout.rs
+++ b/vmbase/src/layout.rs
@@ -16,15 +16,28 @@
pub mod crosvm;
-use crate::console::BASE_ADDRESS;
use crate::linker::__stack_chk_guard;
+use crate::memory::{page_4kb_of, PAGE_SIZE};
use aarch64_paging::paging::VirtualAddress;
use core::ops::Range;
use core::ptr::addr_of;
+use static_assertions::const_assert_eq;
/// 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 {
@@ -86,11 +99,9 @@
linker_region!(eh_stack_limit, bss_end)
}
-/// UART console range.
-pub fn console_uart_range() -> Range<VirtualAddress> {
- const CONSOLE_LEN: usize = 1; // `uart::Uart` only uses one u8 register.
-
- VirtualAddress(BASE_ADDRESS)..VirtualAddress(BASE_ADDRESS + CONSOLE_LEN)
+/// Range of the page at UART_PAGE_ADDR of PAGE_SIZE.
+pub fn console_uart_page() -> Range<VirtualAddress> {
+ VirtualAddress(UART_PAGE_ADDR)..VirtualAddress(UART_PAGE_ADDR + PAGE_SIZE)
}
/// Read-write data (original).
diff --git a/vmbase/src/memory/shared.rs b/vmbase/src/memory/shared.rs
index 5a25d9f..d869b16 100644
--- a/vmbase/src/memory/shared.rs
+++ b/vmbase/src/memory/shared.rs
@@ -17,11 +17,11 @@
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::util::{page_4kb_of, virt_to_phys};
-use crate::console;
+use super::util::virt_to_phys;
use crate::dsb;
use crate::exceptions::HandleExceptionError;
use crate::hyp::{self, get_mem_sharer, get_mmio_guard};
+use crate::layout;
use crate::util::unchecked_align_down;
use crate::util::RangeExt as _;
use aarch64_paging::paging::{
@@ -412,7 +412,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 == page_4kb_of(console::BASE_ADDRESS) {
+ if self.frames.contains(&base) || base == layout::UART_PAGE_ADDR {
return Err(MemoryTrackerError::DuplicateMmioShare(base));
}