blob: 37e2af80ef5ca157ccc3fa11718c6f91874da28e [file] [log] [blame]
// Copyright 2023, 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.
//! Support for the debug policy overlay in pvmfw
use alloc::vec;
use core::ffi::CStr;
use core::fmt;
use libfdt::FdtError;
use log::info;
#[derive(Debug, Clone)]
pub enum DebugPolicyError {
/// The provided baseline FDT was invalid or malformed, so cannot access certain node/prop
Fdt(&'static str, FdtError),
/// The provided debug policy FDT was invalid or malformed.
DebugPolicyFdt(&'static str, FdtError),
/// The overlaid result FDT is invalid or malformed, and may be corrupted.
OverlaidFdt(&'static str, FdtError),
}
impl fmt::Display for DebugPolicyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Fdt(s, e) => write!(f, "Invalid baseline FDT. {s}: {e}"),
Self::DebugPolicyFdt(s, e) => write!(f, "Invalid overlay FDT. {s}: {e}"),
Self::OverlaidFdt(s, e) => write!(f, "Invalid overlaid FDT. {s}: {e}"),
}
}
}
/// Applies the debug policy device tree overlay to the pVM DT.
///
/// # Safety
///
/// When an error is returned by this function, the input `Fdt` should be
/// discarded as it may have have been partially corrupted during the overlay
/// application process.
unsafe fn apply_debug_policy(
fdt: &mut libfdt::Fdt,
debug_policy: &mut [u8],
) -> Result<(), DebugPolicyError> {
let overlay = libfdt::Fdt::from_mut_slice(debug_policy)
.map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to load debug policy overlay", e))?;
fdt.unpack().map_err(|e| DebugPolicyError::Fdt("Failed to unpack", e))?;
let fdt = fdt
.apply_overlay(overlay)
.map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to apply overlay", e))?;
fdt.pack().map_err(|e| DebugPolicyError::OverlaidFdt("Failed to re-pack", e))
}
/// Dsiables ramdump by removing crashkernel from bootargs in /chosen.
///
/// # Safety
///
/// This may corrupt the input `Fdt` when error happens while editing prop value.
unsafe fn disable_ramdump(fdt: &mut libfdt::Fdt) -> Result<(), DebugPolicyError> {
let chosen_path = CStr::from_bytes_with_nul(b"/chosen\0").unwrap();
let bootargs_name = CStr::from_bytes_with_nul(b"bootargs\0").unwrap();
let chosen = match fdt
.node(chosen_path)
.map_err(|e| DebugPolicyError::Fdt("Failed to find /chosen", e))?
{
Some(node) => node,
None => return Ok(()),
};
let bootargs = match chosen
.getprop_str(bootargs_name)
.map_err(|e| DebugPolicyError::Fdt("Failed to find bootargs prop", e))?
{
Some(value) if !value.to_bytes().is_empty() => value,
_ => return Ok(()),
};
// TODO: Improve add 'crashkernel=17MB' only when it's unnecessary.
// Currently 'crashkernel=17MB' in virtualizationservice and passed by
// chosen node, because it's not exactly a debug policy but a
// configuration. However, it's actually microdroid specific
// so we need a way to generalize it.
let mut args = vec![];
for arg in bootargs.to_bytes().split(|byte| byte.is_ascii_whitespace()) {
if arg.is_empty() || arg.starts_with(b"crashkernel=") {
continue;
}
args.push(arg);
}
let mut new_bootargs = args.as_slice().join(&b" "[..]);
new_bootargs.push(b'\0');
// We've checked existence of /chosen node at the beginning.
let mut chosen_mut = fdt.node_mut(chosen_path).unwrap().unwrap();
chosen_mut.setprop(bootargs_name, new_bootargs.as_slice()).map_err(|e| {
DebugPolicyError::OverlaidFdt("Failed to remove crashkernel. FDT might be corrupted", e)
})
}
/// Returns true only if fdt has ramdump prop in the /avf/guest/common node with value <1>
fn is_ramdump_enabled(fdt: &libfdt::Fdt) -> Result<bool, DebugPolicyError> {
let common = match fdt
.node(CStr::from_bytes_with_nul(b"/avf/guest/common\0").unwrap())
.map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find /avf/guest/common node", e))?
{
Some(node) => node,
None => return Ok(false),
};
match common
.getprop_u32(CStr::from_bytes_with_nul(b"ramdump\0").unwrap())
.map_err(|e| DebugPolicyError::DebugPolicyFdt("Failed to find ramdump prop", e))?
{
Some(1) => Ok(true),
_ => Ok(false),
}
}
/// Handles debug policies.
///
/// # Safety
///
/// This may corrupt the input `Fdt` when overlaying debug policy or applying
/// ramdump configuration.
pub unsafe fn handle_debug_policy(
fdt: &mut libfdt::Fdt,
debug_policy: Option<&mut [u8]>,
) -> Result<(), DebugPolicyError> {
if let Some(dp) = debug_policy {
apply_debug_policy(fdt, dp)?;
}
// Handles ramdump in the debug policy
if is_ramdump_enabled(fdt)? {
info!("ramdump is enabled by debug policy");
return Ok(());
}
disable_ramdump(fdt)
}