use super::services::ArcMutexChipsetServices;
use crate::BusIdPci;
use crate::VmmChipsetDevice;
use arc_cyclic_builder::ArcCyclicBuilder;
use arc_cyclic_builder::ArcCyclicBuilderExt;
use chipset_device::mmio::RegisterMmioIntercept;
use chipset_device::pio::RegisterPortIoIntercept;
use closeable_mutex::CloseableMutex;
use std::future::Future;
use std::sync::Arc;
use std::sync::Weak;
use thiserror::Error;
use tracing::instrument;
#[derive(Debug, Error)]
pub(crate) enum AddDeviceErrorKind {
#[error("could not construct device")]
DeviceError(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("no pci bus address provided")]
NoPciBusAddress,
#[error("error finalizing device")]
Finalize(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}
impl AddDeviceErrorKind {
pub(crate) fn with_dev_name(self, dev_name: Arc<str>) -> AddDeviceError {
AddDeviceError {
dev_name,
inner: self,
}
}
}
#[derive(Debug, Error)]
#[error("could not initialize {dev_name}")]
pub struct AddDeviceError {
dev_name: Arc<str>,
#[source]
inner: AddDeviceErrorKind,
}
pub trait ArcMutexChipsetServicesFinalize<T> {
type Error;
fn finalize(self, dev: &Arc<CloseableMutex<T>>, name: Arc<str>) -> Result<(), Self::Error>;
}
pub struct ArcMutexChipsetDeviceBuilder<'a, 'b, T> {
services: ArcMutexChipsetServices<'a, 'b>,
arc_builder: ArcCyclicBuilder<CloseableMutex<T>>,
dev_name: Arc<str>,
pci_addr: Option<(u8, u8, u8)>,
pci_bus_id: Option<BusIdPci>,
external_pci: bool,
}
impl<'a, 'b, T> ArcMutexChipsetDeviceBuilder<'a, 'b, T>
where
T: VmmChipsetDevice,
{
pub fn new(
name: Arc<str>,
new_device_services: impl FnOnce(
Weak<CloseableMutex<T>>,
Arc<str>,
) -> ArcMutexChipsetServices<'a, 'b>,
) -> Self {
let arc_builder: ArcCyclicBuilder<CloseableMutex<T>> = Arc::new_cyclic_builder();
let services = (new_device_services)(arc_builder.weak(), name.clone());
ArcMutexChipsetDeviceBuilder {
services,
arc_builder,
dev_name: name,
pci_addr: None,
pci_bus_id: None,
external_pci: false,
}
}
pub fn omit_saved_state(mut self) -> Self {
self.services.omit_saved_state();
self
}
pub fn with_pci_addr(mut self, bus: u8, device: u8, function: u8) -> Self {
self.pci_addr = Some((bus, device, function));
self
}
pub fn on_pci_bus(mut self, id: BusIdPci) -> Self {
self.pci_bus_id = Some(id);
self
}
pub fn with_external_pci(mut self) -> Self {
self.external_pci = true;
self
}
fn inner_add(
mut self,
typed_dev: Result<T, AddDeviceError>,
) -> Result<Arc<CloseableMutex<T>>, AddDeviceError> {
let mut typed_dev = typed_dev?;
if let Some(dev) = typed_dev.supports_mmio() {
for (label, range) in dev.get_static_regions() {
self.services
.register_mmio()
.new_io_region(label, range.end() - range.start() + 1)
.map(*range.start());
}
}
if let Some(dev) = typed_dev.supports_pio() {
for (label, range) in dev.get_static_regions() {
self.services
.register_pio()
.new_io_region(label, range.end() - range.start() + 1)
.map(*range.start());
}
}
if !self.external_pci {
if let Some(dev) = typed_dev.supports_pci() {
let bdf = match (self.pci_addr, dev.suggested_bdf()) {
(Some(override_bdf), Some(suggested_bdf)) => {
let (ob, od, of) = override_bdf;
let (sb, sd, sf) = suggested_bdf;
tracing::info!(
"overriding suggested bdf: using {:02x}:{:02x}:{} instead of {:02x}:{:02x}:{}",
ob, od, of,
sb, sd, sf
);
override_bdf
}
(None, Some(bdf)) | (Some(bdf), None) => bdf,
(None, None) => {
return Err(AddDeviceErrorKind::NoPciBusAddress.with_dev_name(self.dev_name))
}
};
let bus_id = match self.pci_bus_id.take() {
Some(bus_id) => bus_id,
None => panic!(
"wiring error: did not invoke `on_pci_bus` for `{}`",
self.dev_name
),
};
self.services.register_static_pci(bus_id, bdf);
}
}
let dev = self.arc_builder.build(CloseableMutex::new(typed_dev));
self.services
.finalize(&dev, self.dev_name.clone())
.map_err(|err| AddDeviceErrorKind::Finalize(err.into()).with_dev_name(self.dev_name))?;
Ok(dev)
}
#[allow(clippy::should_implement_trait)] #[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
pub fn add<F>(mut self, f: F) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
where
F: FnOnce(&mut ArcMutexChipsetServices<'a, 'b>) -> T,
{
let dev = (f)(&mut self.services);
self.inner_add(Ok(dev))
}
#[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
pub async fn add_async<F, Fut>(mut self, f: F) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
where
F: for<'c> FnOnce(&'c mut ArcMutexChipsetServices<'a, 'b>) -> Fut,
Fut: Future<Output = T>,
{
let dev = (f)(&mut self.services).await;
self.inner_add(Ok(dev))
}
#[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
pub fn try_add<F, E>(mut self, f: F) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
where
F: FnOnce(&mut ArcMutexChipsetServices<'a, 'b>) -> Result<T, E>,
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
let dev = match (f)(&mut self.services) {
Ok(dev) => dev,
Err(e) => {
return Err(AddDeviceErrorKind::DeviceError(e.into()).with_dev_name(self.dev_name))
}
};
self.inner_add(Ok(dev))
}
#[instrument(name = "add_device", skip_all, fields(device = self.dev_name.as_ref()))]
pub async fn try_add_async<F, Fut, E>(
mut self,
f: F,
) -> Result<Arc<CloseableMutex<T>>, AddDeviceError>
where
F: for<'c> FnOnce(&'c mut ArcMutexChipsetServices<'a, 'b>) -> Fut,
Fut: Future<Output = Result<T, E>>,
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
let dev = match (f)(&mut self.services).await {
Ok(dev) => dev,
Err(e) => {
return Err(AddDeviceErrorKind::DeviceError(e.into()).with_dev_name(self.dev_name))
}
};
self.inner_add(Ok(dev))
}
pub fn services(&mut self) -> &mut ArcMutexChipsetServices<'a, 'b> {
&mut self.services
}
}