Skip to content

Commit

Permalink
user_driver: introduce new DMA trait for future DMA infrastructure ch…
Browse files Browse the repository at this point in the history
…anges (#767)

Introduces the initial implementation of the DMA framework, providing a
centralized mechanism for DMA-capable memory allocation. It establishes
the foundation for managing direct memory access (DMA) operations
efficiently in a unified manner. Future enhancements will include
pin/unpin support and bounce buffer management.

This serves as a stepping stone toward enabling pin/unpin support for
the platform, which will be introduced in an upcoming PR.

---------

Co-authored-by: Chris Oo <[email protected]>
  • Loading branch information
bhargavshah1988 and chris-oo authored Feb 6, 2025
1 parent feb548c commit 2ad108b
Show file tree
Hide file tree
Showing 18 changed files with 255 additions and 135 deletions.
7 changes: 5 additions & 2 deletions openhcl/underhill_core/src/dispatch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod pci_shutdown;
pub mod vtl2_settings_worker;

use self::vtl2_settings_worker::DeviceInterfaces;
use crate::dma_manager::DmaClientSpawner;
use crate::dma_manager::GlobalDmaManager;
use crate::emuplat::netvsp::RuntimeSavedState;
use crate::emuplat::EmuplatServicing;
use crate::nvme_manager::NvmeManager;
Expand Down Expand Up @@ -105,10 +107,10 @@ pub trait LoadedVmNetworkSettings: Inspect {
threadpool: &AffinitizedThreadpool,
uevent_listener: &UeventListener,
servicing_netvsp_state: &Option<Vec<crate::emuplat::netvsp::SavedState>>,
shared_vis_pages_pool: &Option<PagePool>,
partition: Arc<UhPartition>,
state_units: &StateUnits,
vmbus_server: &Option<VmbusServerHandle>,
dma_client_spawner: DmaClientSpawner,
) -> anyhow::Result<RuntimeSavedState>;

/// Callback when network is removed externally.
Expand Down Expand Up @@ -181,6 +183,7 @@ pub(crate) struct LoadedVm {
pub private_pool: Option<PagePool>,
pub nvme_keep_alive: bool,
pub test_configuration: Option<TestScenarioConfig>,
pub dma_manager: GlobalDmaManager,
}

pub struct LoadedVmState<T> {
Expand Down Expand Up @@ -747,10 +750,10 @@ impl LoadedVm {
threadpool,
&self.uevent_listener,
&None, // VF getting added; no existing state
&self.shared_vis_pool,
self.partition.clone(),
&self.state_units,
&self.vmbus_server,
self.dma_manager.get_client_spawner().clone(),
)
.await?;

Expand Down
96 changes: 96 additions & 0 deletions openhcl/underhill_core/src/dma_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! This module provides a global DMA manager and client implementation.
//! It manages DMA buffers and provides clients with access to these buffers.
//! The `GlobalDmaManager` creates DMA buffers for different devices.
//! The `DmaClientImpl` struct implements the `user_driver::DmaClient` trait.
use parking_lot::Mutex;
use std::sync::Arc;
use user_driver::memory::MemoryBlock;
use user_driver::vfio::VfioDmaBuffer;

pub struct GlobalDmaManager {
inner: Arc<Mutex<GlobalDmaManagerInner>>,
}

pub struct GlobalDmaManagerInner {
dma_buffer_spawner: Box<dyn Fn(String) -> anyhow::Result<Arc<dyn VfioDmaBuffer>> + Send>,
}

impl GlobalDmaManager {
/// Creates a new `GlobalDmaManager` with the given DMA buffer spawner.
pub fn new(
dma_buffer_spawner: Box<dyn Fn(String) -> anyhow::Result<Arc<dyn VfioDmaBuffer>> + Send>,
) -> Self {
let inner = GlobalDmaManagerInner { dma_buffer_spawner };

GlobalDmaManager {
inner: Arc::new(Mutex::new(inner)),
}
}

fn create_client_internal(
inner: &Arc<Mutex<GlobalDmaManagerInner>>,
device_name: String,
) -> anyhow::Result<Arc<DmaClientImpl>> {
let manager_inner = inner.lock();
let allocator = {
// Access the page_pool and call its allocator method directly
(manager_inner.dma_buffer_spawner)(device_name)
.map_err(|e| anyhow::anyhow!("failed to get DMA buffer allocator: {:?}", e))?
};

let client = DmaClientImpl {
_dma_manager_inner: inner.clone(),
dma_buffer_allocator: Some(allocator.clone()), // Set the allocator now
};

let arc_client = Arc::new(client);

Ok(arc_client)
}

/// Returns a `DmaClientSpawner` for creating DMA clients.
pub fn get_client_spawner(&self) -> DmaClientSpawner {
DmaClientSpawner {
dma_manager_inner: self.inner.clone(),
}
}
}

pub struct DmaClientImpl {
/// This is added to support map/pin functionality in the future.
_dma_manager_inner: Arc<Mutex<GlobalDmaManagerInner>>,
dma_buffer_allocator: Option<Arc<dyn VfioDmaBuffer>>,
}

impl user_driver::DmaClient for DmaClientImpl {
fn allocate_dma_buffer(&self, total_size: usize) -> anyhow::Result<MemoryBlock> {
if self.dma_buffer_allocator.is_none() {
return Err(anyhow::anyhow!("DMA buffer allocator is not set"));
}

let allocator = self.dma_buffer_allocator.as_ref().unwrap();

allocator.create_dma_buffer(total_size)
}

fn attach_dma_buffer(&self, len: usize, base_pfn: u64) -> anyhow::Result<MemoryBlock> {
let allocator = self.dma_buffer_allocator.as_ref().unwrap();
allocator.restore_dma_buffer(len, base_pfn)
}
}

#[derive(Clone)]
pub struct DmaClientSpawner {
dma_manager_inner: Arc<Mutex<GlobalDmaManagerInner>>,
}

impl DmaClientSpawner {
/// Creates a new DMA client with the given device name.
pub fn create_client(&self, device_name: String) -> anyhow::Result<Arc<DmaClientImpl>> {
GlobalDmaManager::create_client_internal(&self.dma_manager_inner, device_name)
}
}
27 changes: 14 additions & 13 deletions openhcl/underhill_core/src/emuplat/netvsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use uevent::UeventListener;
use user_driver::vfio::vfio_set_device_reset_method;
use user_driver::vfio::PciDeviceResetMethod;
use user_driver::vfio::VfioDevice;
use user_driver::vfio::VfioDmaBuffer;
use user_driver::DmaClient;
use vmcore::vm_task::VmTaskDriverSource;
use vpci::bus_control::VpciBusControl;
use vpci::bus_control::VpciBusEvent;
Expand All @@ -65,7 +65,7 @@ async fn create_mana_device(
pci_id: &str,
vp_count: u32,
max_sub_channels: u16,
dma_buffer: Arc<dyn VfioDmaBuffer>,
dma_client: Arc<dyn DmaClient>,
) -> anyhow::Result<ManaDevice<VfioDevice>> {
// Disable FLR on vfio attach/detach; this allows faster system
// startup/shutdown with the caveat that the device needs to be properly
Expand All @@ -89,7 +89,7 @@ async fn create_mana_device(
pci_id,
vp_count,
max_sub_channels,
&dma_buffer,
dma_client.clone(),
)
.await
{
Expand Down Expand Up @@ -118,9 +118,9 @@ async fn try_create_mana_device(
pci_id: &str,
vp_count: u32,
max_sub_channels: u16,
dma_buffer: &Arc<dyn VfioDmaBuffer>,
dma_client: Arc<dyn DmaClient>,
) -> anyhow::Result<ManaDevice<VfioDevice>> {
let device = VfioDevice::new(driver_source, pci_id, dma_buffer.clone())
let device = VfioDevice::new(driver_source, pci_id, dma_client)
.await
.context("failed to open device")?;

Expand Down Expand Up @@ -200,9 +200,9 @@ struct HclNetworkVFManagerWorker {
vtl2_bus_control: HclVpciBusControl,
vtl2_pci_id: String,
#[inspect(skip)]
dma_buffer: Arc<dyn VfioDmaBuffer>,
#[inspect(skip)]
dma_mode: GuestDmaMode,
#[inspect(skip)]
dma_client: Arc<dyn DmaClient>,
}

impl HclNetworkVFManagerWorker {
Expand All @@ -217,8 +217,8 @@ impl HclNetworkVFManagerWorker {
endpoint_controls: Vec<DisconnectableEndpointControl>,
vp_count: u32,
max_sub_channels: u16,
dma_buffer: Arc<dyn VfioDmaBuffer>,
dma_mode: GuestDmaMode,
dma_client: Arc<dyn DmaClient>,
) -> (Self, mesh::Sender<HclNetworkVfManagerMessage>) {
let (tx_to_worker, worker_rx) = mesh::channel();
let vtl0_bus_control = if save_state.hidden_vtl0.lock().unwrap_or(false) {
Expand Down Expand Up @@ -247,8 +247,8 @@ impl HclNetworkVFManagerWorker {
vtl0_bus_control,
vtl2_bus_control,
vtl2_pci_id,
dma_buffer,
dma_mode,
dma_client,
},
tx_to_worker,
)
Expand Down Expand Up @@ -685,12 +685,13 @@ impl HclNetworkVFManagerWorker {
} else {
tracing::info!("VTL2 VF arrived");
}

let device_bound = match create_mana_device(
&self.driver_source,
&self.vtl2_pci_id,
self.vp_count,
self.max_sub_channels,
self.dma_buffer.clone(),
self.dma_client.clone(),
)
.await
{
Expand Down Expand Up @@ -865,8 +866,8 @@ impl HclNetworkVFManager {
vp_count: u32,
max_sub_channels: u16,
netvsp_state: &Option<Vec<SavedState>>,
dma_buffer: Arc<dyn VfioDmaBuffer>,
dma_mode: GuestDmaMode,
dma_client: Arc<dyn DmaClient>,
) -> anyhow::Result<(
Self,
Vec<HclNetworkVFManagerEndpointInfo>,
Expand All @@ -877,7 +878,7 @@ impl HclNetworkVFManager {
&vtl2_pci_id,
vp_count,
max_sub_channels,
dma_buffer.clone(),
dma_client.clone(),
)
.await?;
let (mut endpoints, endpoint_controls): (Vec<_>, Vec<_>) = (0..device.num_vports())
Expand Down Expand Up @@ -926,8 +927,8 @@ impl HclNetworkVFManager {
endpoint_controls,
vp_count,
max_sub_channels,
dma_buffer,
dma_mode,
dma_client,
);

// Queue new endpoints.
Expand Down
1 change: 1 addition & 0 deletions openhcl/underhill_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

mod diag;
mod dispatch;
mod dma_manager;
mod emuplat;
mod get_tracing;
mod inspect_internal;
Expand Down
41 changes: 21 additions & 20 deletions openhcl/underhill_core/src/nvme_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! Provides access to NVMe namespaces that are backed by the user-mode NVMe
//! VFIO driver. Keeps track of all the NVMe drivers.
use crate::dma_manager::DmaClientSpawner;
use crate::nvme_manager::save_restore::NvmeManagerSavedState;
use crate::nvme_manager::save_restore::NvmeSavedDiskConfig;
use crate::servicing::NvmeSavedState;
Expand All @@ -26,7 +27,6 @@ use std::sync::Arc;
use thiserror::Error;
use tracing::Instrument;
use user_driver::vfio::VfioDevice;
use user_driver::vfio::VfioDmaBuffer;
use vm_resource::kind::DiskHandleKind;
use vm_resource::AsyncResolveResource;
use vm_resource::ResourceId;
Expand All @@ -47,8 +47,8 @@ enum InnerError {
Vfio(#[source] anyhow::Error),
#[error("failed to initialize nvme device")]
DeviceInitFailed(#[source] anyhow::Error),
#[error("failed to create dma buffer for device")]
DmaBuffer(#[source] anyhow::Error),
#[error("failed to create dma client for device")]
DmaClient(#[source] anyhow::Error),
#[error("failed to get namespace {nsid}")]
Namespace {
nsid: u32,
Expand Down Expand Up @@ -89,18 +89,18 @@ impl NvmeManager {
pub fn new(
driver_source: &VmTaskDriverSource,
vp_count: u32,
dma_buffer_spawner: Box<dyn Fn(String) -> anyhow::Result<Arc<dyn VfioDmaBuffer>> + Send>,
save_restore_supported: bool,
saved_state: Option<NvmeSavedState>,
dma_client_spawner: DmaClientSpawner,
) -> Self {
let (send, recv) = mesh::channel();
let driver = driver_source.simple();
let mut worker = NvmeManagerWorker {
driver_source: driver_source.clone(),
devices: HashMap::new(),
vp_count,
dma_buffer_spawner,
save_restore_supported,
dma_client_spawner,
};
let task = driver.spawn("nvme-manager", async move {
// Restore saved data (if present) before async worker thread runs.
Expand Down Expand Up @@ -209,13 +209,11 @@ struct NvmeManagerWorker {
driver_source: VmTaskDriverSource,
#[inspect(iter_by_key)]
devices: HashMap<String, nvme_driver::NvmeDriver<VfioDevice>>,
// TODO: Revisit this Box<fn> into maybe a trait, once we refactor DMA to a
// central manager.
#[inspect(skip)]
dma_buffer_spawner: Box<dyn Fn(String) -> anyhow::Result<Arc<dyn VfioDmaBuffer>> + Send>,
vp_count: u32,
/// Running environment (memory layout) allows save/restore.
save_restore_supported: bool,
#[inspect(skip)]
dma_client_spawner: DmaClientSpawner,
}

impl NvmeManagerWorker {
Expand Down Expand Up @@ -292,15 +290,15 @@ impl NvmeManagerWorker {
let driver = match self.devices.entry(pci_id.to_owned()) {
hash_map::Entry::Occupied(entry) => entry.into_mut(),
hash_map::Entry::Vacant(entry) => {
let device = VfioDevice::new(
&self.driver_source,
entry.key(),
(self.dma_buffer_spawner)(format!("nvme_{}", entry.key()))
.map_err(InnerError::DmaBuffer)?,
)
.instrument(tracing::info_span!("vfio_device_open", pci_id))
.await
.map_err(InnerError::Vfio)?;
let dma_client = self
.dma_client_spawner
.create_client(format!("nvme_{}", pci_id))
.map_err(InnerError::DmaClient)?;

let device = VfioDevice::new(&self.driver_source, entry.key(), dma_client)
.instrument(tracing::info_span!("vfio_device_open", pci_id))
.await
.map_err(InnerError::Vfio)?;

let driver =
nvme_driver::NvmeDriver::new(&self.driver_source, self.vp_count, device)
Expand Down Expand Up @@ -353,16 +351,19 @@ impl NvmeManagerWorker {
self.devices = HashMap::new();
for disk in &saved_state.nvme_disks {
let pci_id = disk.pci_id.clone();

let dma_client = self
.dma_client_spawner
.create_client(format!("nvme_{}", pci_id))?;
let vfio_device =
// This code can wait on each VFIO device until it is arrived.
// A potential optimization would be to delay VFIO operation
// until it is ready, but a redesign of VfioDevice is needed.
VfioDevice::restore(
&self.driver_source,
&disk.pci_id.clone(),
(self.dma_buffer_spawner)(format!("nvme_{}", pci_id))
.map_err(InnerError::DmaBuffer)?,
true,
dma_client,
)
.instrument(tracing::info_span!("vfio_device_restore", pci_id))
.await?;
Expand Down
2 changes: 1 addition & 1 deletion openhcl/underhill_core/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ impl Options {
.parse::<TestScenarioConfig>()
.map_err(|e| {
tracing::warn!(
"Failed to parse OPENHCL_TEST_CONFIG: {}. No test will be simulated.",
"failed to parse OPENHCL_TEST_CONFIG: {}. No test will be simulated.",
e
)
})
Expand Down
Loading

0 comments on commit 2ad108b

Please sign in to comment.