#![cfg(target_os = "linux")]
#![forbid(unsafe_code)]
use anyhow::Context;
use hcl_mapper::HclMapper;
use inspect::Inspect;
use lower_vtl_permissions_guard::LowerVtlMemorySpawner;
use memory_range::MemoryRange;
use page_pool_alloc::PagePool;
use page_pool_alloc::PagePoolAllocator;
use page_pool_alloc::PagePoolAllocatorSpawner;
use std::sync::Arc;
use user_driver::DmaClient;
use user_driver::lockmem::LockedMemorySpawner;
pub mod save_restore {
use super::OpenhclDmaManager;
use mesh::payload::Protobuf;
use page_pool_alloc::save_restore::PagePoolState;
use vmcore::save_restore::RestoreError;
use vmcore::save_restore::SaveError;
use vmcore::save_restore::SaveRestore;
#[derive(Protobuf)]
#[mesh(package = "openhcl.openhcldmamanager")]
pub struct OpenhclDmaManagerState {
#[mesh(1)]
shared_pool: Option<PagePoolState>,
#[mesh(2)]
private_pool: Option<PagePoolState>,
}
impl SaveRestore for OpenhclDmaManager {
type SavedState = OpenhclDmaManagerState;
fn save(&mut self) -> Result<Self::SavedState, SaveError> {
let shared_pool = self
.shared_pool
.as_mut()
.map(SaveRestore::save)
.transpose()
.map_err(|e| {
SaveError::ChildError("shared pool save failed".into(), Box::new(e))
})?;
let private_pool = self
.private_pool
.as_mut()
.map(SaveRestore::save)
.transpose()
.map_err(|e| {
SaveError::ChildError("private pool save failed".into(), Box::new(e))
})?;
Ok(OpenhclDmaManagerState {
shared_pool,
private_pool,
})
}
fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
match (state.shared_pool, self.shared_pool.as_mut()) {
(None, None) => {}
(Some(_), None) => {
return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
"saved state for shared pool but no shared pool"
)));
}
(None, Some(_)) => {
}
(Some(state), Some(pool)) => {
pool.restore(state).map_err(|e| {
RestoreError::ChildError("shared pool restore failed".into(), Box::new(e))
})?;
}
}
match (state.private_pool, self.private_pool.as_mut()) {
(None, None) => {}
(Some(_), None) => {
return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
"saved state for private pool but no private pool"
)));
}
(None, Some(_)) => {
}
(Some(state), Some(pool)) => {
pool.restore(state).map_err(|e| {
RestoreError::ChildError("private pool restore failed".into(), Box::new(e))
})?;
}
}
Ok(())
}
}
}
#[derive(Inspect)]
pub struct OpenhclDmaManager {
shared_pool: Option<PagePool>,
private_pool: Option<PagePool>,
#[inspect(skip)]
inner: Arc<DmaManagerInner>,
}
#[derive(Inspect)]
pub enum LowerVtlPermissionPolicy {
Any,
Vtl0,
}
#[derive(Copy, Clone, Inspect)]
pub enum AllocationVisibility {
Shared,
Private,
}
#[derive(Inspect)]
pub struct DmaClientParameters {
pub device_name: String,
pub lower_vtl_policy: LowerVtlPermissionPolicy,
pub allocation_visibility: AllocationVisibility,
pub persistent_allocations: bool,
}
struct DmaManagerInner {
shared_spawner: Option<PagePoolAllocatorSpawner>,
private_spawner: Option<PagePoolAllocatorSpawner>,
lower_vtl: Arc<DmaManagerLowerVtl>,
}
struct DmaManagerLowerVtl {
mshv_hvcall: hcl::ioctl::MshvHvcall,
}
impl DmaManagerLowerVtl {
pub fn new() -> anyhow::Result<Arc<Self>> {
let mshv_hvcall = hcl::ioctl::MshvHvcall::new().context("failed to open mshv_hvcall")?;
mshv_hvcall.set_allowed_hypercalls(&[hvdef::HypercallCode::HvCallModifyVtlProtectionMask]);
Ok(Arc::new(Self { mshv_hvcall }))
}
}
impl virt::VtlMemoryProtection for DmaManagerLowerVtl {
fn modify_vtl_page_setting(&self, pfn: u64, flags: hvdef::HvMapGpaFlags) -> anyhow::Result<()> {
self.mshv_hvcall
.modify_vtl_protection_mask(
MemoryRange::from_4k_gpn_range(pfn..pfn + 1),
flags,
hvdef::hypercall::HvInputVtl::CURRENT_VTL,
)
.context("failed to modify VTL page permissions")
}
}
impl DmaManagerInner {
fn new_dma_client(&self, params: DmaClientParameters) -> anyhow::Result<Arc<OpenhclDmaClient>> {
let backing = {
let DmaClientParameters {
device_name,
lower_vtl_policy,
allocation_visibility,
persistent_allocations,
} = ¶ms;
struct ClientCreation<'a> {
allocation_visibility: AllocationVisibility,
persistent_allocations: bool,
shared_spawner: Option<&'a PagePoolAllocatorSpawner>,
private_spawner: Option<&'a PagePoolAllocatorSpawner>,
}
let creation = ClientCreation {
allocation_visibility: *allocation_visibility,
persistent_allocations: *persistent_allocations,
shared_spawner: self.shared_spawner.as_ref(),
private_spawner: self.private_spawner.as_ref(),
};
match creation {
ClientCreation {
allocation_visibility: AllocationVisibility::Shared,
persistent_allocations: _,
shared_spawner: Some(shared),
private_spawner: _,
} => {
DmaClientBacking::SharedPool(
shared
.allocator(device_name.into())
.context("failed to create shared allocator")?,
)
}
ClientCreation {
allocation_visibility: AllocationVisibility::Shared,
persistent_allocations: _,
shared_spawner: None,
private_spawner: _,
} => {
anyhow::bail!("no sources available for shared visibility")
}
ClientCreation {
allocation_visibility: AllocationVisibility::Private,
persistent_allocations: true,
shared_spawner: _,
private_spawner: Some(private),
} => match lower_vtl_policy {
LowerVtlPermissionPolicy::Any => {
DmaClientBacking::PrivatePool(
private
.allocator(device_name.into())
.context("failed to create private allocator")?,
)
}
LowerVtlPermissionPolicy::Vtl0 => {
DmaClientBacking::PrivatePoolLowerVtl(LowerVtlMemorySpawner::new(
private
.allocator(device_name.into())
.context("failed to create private allocator")?,
self.lower_vtl.clone(),
))
}
},
ClientCreation {
allocation_visibility: AllocationVisibility::Private,
persistent_allocations: true,
shared_spawner: _,
private_spawner: None,
} => {
anyhow::bail!("no sources available for private persistent allocations")
}
ClientCreation {
allocation_visibility: AllocationVisibility::Private,
persistent_allocations: false,
shared_spawner: _,
private_spawner: _,
} => match lower_vtl_policy {
LowerVtlPermissionPolicy::Any => {
DmaClientBacking::LockedMemory(LockedMemorySpawner)
}
LowerVtlPermissionPolicy::Vtl0 => {
DmaClientBacking::LockedMemoryLowerVtl(LowerVtlMemorySpawner::new(
LockedMemorySpawner,
self.lower_vtl.clone(),
))
}
},
}
};
Ok(Arc::new(OpenhclDmaClient { backing, params }))
}
}
impl OpenhclDmaManager {
pub fn new(
shared_ranges: &[MemoryRange],
private_ranges: &[MemoryRange],
vtom: u64,
) -> anyhow::Result<Self> {
let shared_pool = if shared_ranges.is_empty() {
None
} else {
Some(
PagePool::new(
shared_ranges,
HclMapper::new_shared(vtom).context("failed to create hcl mapper")?,
)
.context("failed to create shared page pool")?,
)
};
let private_pool = if private_ranges.is_empty() {
None
} else {
Some(
PagePool::new(
private_ranges,
HclMapper::new_private().context("failed to create hcl mapper")?,
)
.context("failed to create private page pool")?,
)
};
Ok(OpenhclDmaManager {
inner: Arc::new(DmaManagerInner {
shared_spawner: shared_pool.as_ref().map(|pool| pool.allocator_spawner()),
private_spawner: private_pool.as_ref().map(|pool| pool.allocator_spawner()),
lower_vtl: DmaManagerLowerVtl::new().context("failed to create lower vtl")?,
}),
shared_pool,
private_pool,
})
}
pub fn new_client(&self, params: DmaClientParameters) -> anyhow::Result<Arc<OpenhclDmaClient>> {
self.inner.new_dma_client(params)
}
pub fn client_spawner(&self) -> DmaClientSpawner {
DmaClientSpawner {
inner: self.inner.clone(),
}
}
pub fn validate_restore(&self) -> anyhow::Result<()> {
if let Some(shared_pool) = &self.shared_pool {
shared_pool
.validate_restore(false)
.context("failed to validate restore for shared pool")?
}
if let Some(private_pool) = &self.private_pool {
private_pool
.validate_restore(false)
.context("failed to validate restore for private pool")?
}
Ok(())
}
}
#[derive(Clone)]
pub struct DmaClientSpawner {
inner: Arc<DmaManagerInner>,
}
impl DmaClientSpawner {
pub fn new_client(&self, params: DmaClientParameters) -> anyhow::Result<Arc<OpenhclDmaClient>> {
self.inner.new_dma_client(params)
}
}
#[derive(Inspect)]
#[inspect(tag = "type")]
enum DmaClientBacking {
SharedPool(#[inspect(skip)] PagePoolAllocator),
PrivatePool(#[inspect(skip)] PagePoolAllocator),
LockedMemory(#[inspect(skip)] LockedMemorySpawner),
PrivatePoolLowerVtl(#[inspect(skip)] LowerVtlMemorySpawner<PagePoolAllocator>),
LockedMemoryLowerVtl(#[inspect(skip)] LowerVtlMemorySpawner<LockedMemorySpawner>),
}
impl DmaClientBacking {
fn allocate_dma_buffer(
&self,
total_size: usize,
) -> anyhow::Result<user_driver::memory::MemoryBlock> {
match self {
DmaClientBacking::SharedPool(allocator) => allocator.allocate_dma_buffer(total_size),
DmaClientBacking::PrivatePool(allocator) => allocator.allocate_dma_buffer(total_size),
DmaClientBacking::LockedMemory(spawner) => spawner.allocate_dma_buffer(total_size),
DmaClientBacking::PrivatePoolLowerVtl(spawner) => {
spawner.allocate_dma_buffer(total_size)
}
DmaClientBacking::LockedMemoryLowerVtl(spawner) => {
spawner.allocate_dma_buffer(total_size)
}
}
}
fn attach_dma_buffer(
&self,
len: usize,
base_pfn: u64,
) -> anyhow::Result<user_driver::memory::MemoryBlock> {
match self {
DmaClientBacking::SharedPool(allocator) => allocator.attach_dma_buffer(len, base_pfn),
DmaClientBacking::PrivatePool(allocator) => allocator.attach_dma_buffer(len, base_pfn),
DmaClientBacking::LockedMemory(spawner) => spawner.attach_dma_buffer(len, base_pfn),
DmaClientBacking::PrivatePoolLowerVtl(spawner) => {
spawner.attach_dma_buffer(len, base_pfn)
}
DmaClientBacking::LockedMemoryLowerVtl(spawner) => {
spawner.attach_dma_buffer(len, base_pfn)
}
}
}
}
#[derive(Inspect)]
pub struct OpenhclDmaClient {
backing: DmaClientBacking,
params: DmaClientParameters,
}
impl DmaClient for OpenhclDmaClient {
fn allocate_dma_buffer(
&self,
total_size: usize,
) -> anyhow::Result<user_driver::memory::MemoryBlock> {
self.backing.allocate_dma_buffer(total_size)
}
fn attach_dma_buffer(
&self,
len: usize,
base_pfn: u64,
) -> anyhow::Result<user_driver::memory::MemoryBlock> {
self.backing.attach_dma_buffer(len, base_pfn)
}
}