blob: c05c72c89f727c697d8c49b32c583600852c43d5 [file]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::BTreeMap;
use std::sync::Arc;
use acpi_tables::aml::Aml;
use base::syslog;
use base::warn;
use base::AsRawDescriptors;
use base::Tube;
use devices::Bus;
use devices::BusDevice;
use devices::DevicePowerManager;
use devices::IommuDevType;
use devices::IrqChip;
use devices::IrqEventSource;
use devices::ProxyDevice;
use devices::VfioPlatformDevice;
use hypervisor::ProtectionType;
use hypervisor::Vm;
use minijail::Minijail;
use resources::AllocOptions;
use resources::SystemAllocator;
use sync::Mutex;
use crate::DeviceRegistrationError;
/// Adds goldfish battery and returns the platform needed resources including
/// its AML data and mmio base address
///
/// # Arguments
///
/// * `amls` - the vector to put the goldfish battery AML
/// * `battery_jail` - used when sandbox is enabled
/// * `mmio_bus` - bus to add the devices to
/// * `irq_chip` - the IrqChip object for registering irq events
/// * `irq_num` - assigned interrupt to use
/// * `resources` - the SystemAllocator to allocate IO and MMIO for acpi
pub fn add_goldfish_battery(
amls: &mut Vec<u8>,
battery_jail: Option<Minijail>,
mmio_bus: &Bus,
irq_chip: &mut dyn IrqChip,
irq_num: u32,
resources: &mut SystemAllocator,
#[cfg(feature = "swap")] swap_controller: &mut Option<swap::SwapController>,
) -> Result<(Tube, u64), DeviceRegistrationError> {
let alloc = resources.get_anon_alloc();
let mmio_base = resources
.allocate_mmio(
devices::bat::GOLDFISHBAT_MMIO_LEN,
alloc,
"GoldfishBattery".to_string(),
AllocOptions::new().align(devices::bat::GOLDFISHBAT_MMIO_LEN),
)
.map_err(DeviceRegistrationError::AllocateIoResource)?;
let (control_tube, response_tube) =
Tube::pair().map_err(DeviceRegistrationError::CreateTube)?;
#[cfg(feature = "power-monitor-powerd")]
let (create_monitor, create_client) = (
Some(
Box::new(power_monitor::powerd::monitor::DBusMonitor::connect)
as Box<dyn power_monitor::CreatePowerMonitorFn>,
),
Some(Box::new(power_monitor::powerd::client::DBusClient::connect)
as Box<dyn power_monitor::CreatePowerClientFn>),
);
#[cfg(not(feature = "power-monitor-powerd"))]
let (create_monitor, create_client) = (None, None);
let irq_evt = devices::IrqLevelEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
let goldfish_bat = devices::GoldfishBattery::new(
mmio_base,
irq_num,
irq_evt
.try_clone()
.map_err(DeviceRegistrationError::EventClone)?,
response_tube,
create_monitor,
create_client,
)
.map_err(DeviceRegistrationError::RegisterBattery)?;
goldfish_bat.to_aml_bytes(amls);
irq_chip
.register_level_irq_event(
irq_num,
&irq_evt,
IrqEventSource::from_device(&goldfish_bat),
)
.map_err(DeviceRegistrationError::RegisterIrqfd)?;
match battery_jail {
#[cfg(not(windows))]
Some(jail) => {
let mut keep_rds = goldfish_bat.keep_rds();
syslog::push_descriptors(&mut keep_rds);
cros_tracing::push_descriptors!(&mut keep_rds);
metrics::push_descriptors(&mut keep_rds);
mmio_bus
.insert(
Arc::new(Mutex::new(
ProxyDevice::new(
goldfish_bat,
jail,
keep_rds,
#[cfg(feature = "swap")]
swap_controller,
)
.map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
)),
mmio_base,
devices::bat::GOLDFISHBAT_MMIO_LEN,
)
.map_err(DeviceRegistrationError::MmioInsert)?;
}
#[cfg(windows)]
Some(_) => {}
None => {
mmio_bus
.insert(
Arc::new(Mutex::new(goldfish_bat)),
mmio_base,
devices::bat::GOLDFISHBAT_MMIO_LEN,
)
.map_err(DeviceRegistrationError::MmioInsert)?;
}
}
Ok((control_tube, mmio_base))
}
pub struct PlatformBusResources {
pub dt_symbol: String, // DT symbol (label) assigned to the device
pub regions: Vec<(u64, u64)>, // (start address, size)
pub irqs: Vec<(u32, u32)>, // (IRQ number, flags)
pub iommus: Vec<(IommuDevType, Option<u32>, Vec<u32>)>, // (IOMMU type, IOMMU identifier, IDs)
pub requires_power_domain: bool,
}
impl PlatformBusResources {
const IRQ_TRIGGER_EDGE: u32 = 1;
const IRQ_TRIGGER_LEVEL: u32 = 4;
fn new(symbol: String) -> Self {
Self {
dt_symbol: symbol,
regions: vec![],
irqs: vec![],
iommus: vec![],
requires_power_domain: false,
}
}
}
/// Creates a platform device for use by this Vm.
#[cfg(any(target_os = "android", target_os = "linux"))]
pub fn generate_platform_bus(
devices: Vec<(VfioPlatformDevice, Option<Minijail>)>,
irq_chip: &mut dyn IrqChip,
mmio_bus: &Bus,
resources: &mut SystemAllocator,
vm: &mut impl Vm,
#[cfg(feature = "swap")] swap_controller: &mut Option<swap::SwapController>,
dev_pm: &mut Option<DevicePowerManager>,
protection_type: ProtectionType,
) -> Result<
(
Vec<Arc<Mutex<dyn BusDevice>>>,
BTreeMap<u32, String>,
Vec<PlatformBusResources>,
),
DeviceRegistrationError,
> {
let mut platform_devices = Vec::new();
let mut pid_labels = BTreeMap::new();
let mut bus_dev_resources = vec![];
// Allocate ranges that may need to be in the Platform MMIO region (MmioType::Platform).
for (mut device, jail) in devices.into_iter() {
let dt_symbol = device
.dt_symbol()
.ok_or(DeviceRegistrationError::MissingDeviceTreeSymbol)?
.to_owned();
let mut device_resources = PlatformBusResources::new(dt_symbol);
let ranges = device
.allocate_regions(resources)
.map_err(DeviceRegistrationError::AllocateIoResource)?;
// If guest memory is private, don't wait for the first access to mmap the device.
if protection_type.isolates_memory() {
device.regions_mmap_early(vm);
}
let mut keep_rds = device.keep_rds();
syslog::push_descriptors(&mut keep_rds);
cros_tracing::push_descriptors!(&mut keep_rds);
metrics::push_descriptors(&mut keep_rds);
let irqs = device
.get_platform_irqs()
.map_err(DeviceRegistrationError::AllocateIrqResource)?;
for irq in irqs.into_iter() {
let irq_num = resources
.allocate_irq()
.ok_or(DeviceRegistrationError::AllocateIrq)?;
if device.irq_is_automask(&irq) {
let irq_evt =
devices::IrqLevelEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
irq_chip
.register_level_irq_event(
irq_num,
&irq_evt,
IrqEventSource::from_device(&device),
)
.map_err(DeviceRegistrationError::RegisterIrqfd)?;
device
.assign_level_platform_irq(&irq_evt, irq.index)
.map_err(DeviceRegistrationError::SetupVfioPlatformIrq)?;
keep_rds.extend(irq_evt.as_raw_descriptors());
device_resources
.irqs
.push((irq_num, PlatformBusResources::IRQ_TRIGGER_LEVEL));
} else {
let irq_evt =
devices::IrqEdgeEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
irq_chip
.register_edge_irq_event(
irq_num,
&irq_evt,
IrqEventSource::from_device(&device),
)
.map_err(DeviceRegistrationError::RegisterIrqfd)?;
device
.assign_edge_platform_irq(&irq_evt, irq.index)
.map_err(DeviceRegistrationError::SetupVfioPlatformIrq)?;
keep_rds.extend(irq_evt.as_raw_descriptors());
device_resources
.irqs
.push((irq_num, PlatformBusResources::IRQ_TRIGGER_EDGE));
}
}
if let Some((iommu_type, id, vsids)) = device.iommu() {
// We currently only support one IOMMU per VFIO device.
device_resources
.iommus
.push((iommu_type, id, vsids.to_vec()));
}
let arced_dev: Arc<Mutex<dyn BusDevice>> = if let Some(jail) = jail {
let proxy = ProxyDevice::new(
device,
jail,
keep_rds,
#[cfg(feature = "swap")]
swap_controller,
)
.map_err(DeviceRegistrationError::ProxyDeviceCreation)?;
pid_labels.insert(proxy.pid() as u32, proxy.debug_label());
Arc::new(Mutex::new(proxy))
} else {
device.on_sandboxed();
Arc::new(Mutex::new(device))
};
platform_devices.push(arced_dev.clone());
for range in &ranges {
mmio_bus
.insert(arced_dev.clone(), range.0, range.1)
.map_err(DeviceRegistrationError::MmioInsert)?;
device_resources.regions.push((range.0, range.1));
}
if let Some(ref mut pm) = dev_pm {
let supports_power_management = arced_dev.lock().supports_power_management();
match supports_power_management {
Err(e) => warn!("Error while detecting PM support, ignoring: {e:#}"),
Ok(false) => {}
Ok(true) => {
// As we currently restrict one device per vPD and to simplify having a PD ID
// which the VMM, hypervisor, and guest recognize, refer to a PD using the base
// of the MMIO of the device it contains.
let domain_id = ranges.first().unwrap().0.try_into().unwrap();
pm.attach(arced_dev.clone(), domain_id)
.map_err(DeviceRegistrationError::AttachDevicePowerDomain)?;
device_resources.requires_power_domain = true;
}
}
}
bus_dev_resources.push(device_resources);
}
Ok((platform_devices, pid_labels, bus_dev_resources))
}