1#![cfg(target_os = "linux")]
9#![forbid(unsafe_code)]
10
11use anyhow::Context;
12use hcl_mapper::HclMapper;
13use inspect::Inspect;
14use lower_vtl_permissions_guard::LowerVtlMemorySpawner;
15use memory_range::MemoryRange;
16use page_pool_alloc::PagePool;
17use page_pool_alloc::PagePoolAllocator;
18use page_pool_alloc::PagePoolAllocatorSpawner;
19use std::sync::Arc;
20use user_driver::DmaClient;
21use user_driver::lockmem::LockedMemorySpawner;
22
23pub mod save_restore {
25 use super::OpenhclDmaManager;
26 use mesh::payload::Protobuf;
27 use page_pool_alloc::save_restore::PagePoolState;
28 use vmcore::save_restore::RestoreError;
29 use vmcore::save_restore::SaveError;
30 use vmcore::save_restore::SaveRestore;
31
32 #[derive(Protobuf)]
34 #[mesh(package = "openhcl.openhcldmamanager")]
35 pub struct OpenhclDmaManagerState {
36 #[mesh(1)]
37 shared_pool: Option<PagePoolState>,
38 #[mesh(2)]
39 private_pool: Option<PagePoolState>,
40 }
41
42 impl SaveRestore for OpenhclDmaManager {
43 type SavedState = OpenhclDmaManagerState;
44
45 fn save(&mut self) -> Result<Self::SavedState, SaveError> {
46 let shared_pool = self
47 .shared_pool
48 .as_mut()
49 .map(SaveRestore::save)
50 .transpose()
51 .map_err(|e| {
52 SaveError::ChildError("shared pool save failed".into(), Box::new(e))
53 })?;
54
55 let private_pool = self
56 .private_pool
57 .as_mut()
58 .map(SaveRestore::save)
59 .transpose()
60 .map_err(|e| {
61 SaveError::ChildError("private pool save failed".into(), Box::new(e))
62 })?;
63
64 Ok(OpenhclDmaManagerState {
65 shared_pool,
66 private_pool,
67 })
68 }
69
70 fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
71 match (state.shared_pool, self.shared_pool.as_mut()) {
72 (None, None) => {}
73 (Some(_), None) => {
74 return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
75 "saved state for shared pool but no shared pool"
76 )));
77 }
78 (None, Some(_)) => {
79 }
82 (Some(state), Some(pool)) => {
83 pool.restore(state).map_err(|e| {
84 RestoreError::ChildError("shared pool restore failed".into(), Box::new(e))
85 })?;
86 }
87 }
88
89 match (state.private_pool, self.private_pool.as_mut()) {
90 (None, None) => {}
91 (Some(_), None) => {
92 return Err(RestoreError::InvalidSavedState(anyhow::anyhow!(
93 "saved state for private pool but no private pool"
94 )));
95 }
96 (None, Some(_)) => {
97 }
100 (Some(state), Some(pool)) => {
101 pool.restore(state).map_err(|e| {
102 RestoreError::ChildError("private pool restore failed".into(), Box::new(e))
103 })?;
104 }
105 }
106
107 Ok(())
108 }
109 }
110}
111
112#[derive(Inspect)]
115pub struct OpenhclDmaManager {
116 shared_pool: Option<PagePool>,
118 private_pool: Option<PagePool>,
120 #[inspect(skip)]
121 inner: Arc<DmaManagerInner>,
122}
123
124#[derive(Inspect)]
126pub enum LowerVtlPermissionPolicy {
127 Any,
129 Vtl0,
131}
132
133#[derive(Copy, Clone, Inspect)]
135pub enum AllocationVisibility {
136 Shared,
138 Private,
140}
141
142#[derive(Inspect)]
144pub struct DmaClientParameters {
145 pub device_name: String,
147 pub lower_vtl_policy: LowerVtlPermissionPolicy,
149 pub allocation_visibility: AllocationVisibility,
151 pub persistent_allocations: bool,
154}
155
156struct DmaManagerInner {
157 shared_spawner: Option<PagePoolAllocatorSpawner>,
158 private_spawner: Option<PagePoolAllocatorSpawner>,
159 lower_vtl: Option<Arc<DmaManagerLowerVtl>>,
160}
161
162struct DmaManagerLowerVtl {
172 mshv_hvcall: hcl::ioctl::MshvHvcall,
173}
174
175impl DmaManagerLowerVtl {
176 pub fn new() -> anyhow::Result<Arc<Self>> {
177 let mshv_hvcall = hcl::ioctl::MshvHvcall::new().context("failed to open mshv_hvcall")?;
178 mshv_hvcall.set_allowed_hypercalls(&[hvdef::HypercallCode::HvCallModifyVtlProtectionMask]);
179 Ok(Arc::new(Self { mshv_hvcall }))
180 }
181}
182
183impl virt::VtlMemoryProtection for DmaManagerLowerVtl {
184 fn modify_vtl_page_setting(&self, pfn: u64, flags: hvdef::HvMapGpaFlags) -> anyhow::Result<()> {
185 self.mshv_hvcall
186 .modify_vtl_protection_mask(
187 MemoryRange::from_4k_gpn_range(pfn..pfn + 1),
188 flags,
189 hvdef::hypercall::HvInputVtl::CURRENT_VTL,
190 )
191 .context("failed to modify VTL page permissions")
192 }
193}
194
195impl DmaManagerInner {
196 fn new_dma_client(&self, params: DmaClientParameters) -> anyhow::Result<Arc<OpenhclDmaClient>> {
197 let backing = {
199 let DmaClientParameters {
200 device_name,
201 lower_vtl_policy,
202 allocation_visibility,
203 persistent_allocations,
204 } = ¶ms;
205
206 struct ClientCreation<'a> {
207 allocation_visibility: AllocationVisibility,
208 persistent_allocations: bool,
209 shared_spawner: Option<&'a PagePoolAllocatorSpawner>,
210 private_spawner: Option<&'a PagePoolAllocatorSpawner>,
211 }
212
213 let creation = ClientCreation {
214 allocation_visibility: *allocation_visibility,
215 persistent_allocations: *persistent_allocations,
216 shared_spawner: self.shared_spawner.as_ref(),
217 private_spawner: self.private_spawner.as_ref(),
218 };
219
220 match creation {
221 ClientCreation {
222 allocation_visibility: AllocationVisibility::Shared,
223 persistent_allocations: _,
224 shared_spawner: Some(shared),
225 private_spawner: _,
226 } => {
227 DmaClientBacking::SharedPool(
232 shared
233 .allocator(device_name.into())
234 .context("failed to create shared allocator")?,
235 )
236 }
237 ClientCreation {
238 allocation_visibility: AllocationVisibility::Shared,
239 persistent_allocations: _,
240 shared_spawner: None,
241 private_spawner: _,
242 } => {
243 anyhow::bail!("no sources available for shared visibility")
245 }
246 ClientCreation {
247 allocation_visibility: AllocationVisibility::Private,
248 persistent_allocations: true,
249 shared_spawner: _,
250 private_spawner: Some(private),
251 } => match lower_vtl_policy {
252 LowerVtlPermissionPolicy::Any => {
253 DmaClientBacking::PrivatePool(
256 private
257 .allocator(device_name.into())
258 .context("failed to create private allocator")?,
259 )
260 }
261 LowerVtlPermissionPolicy::Vtl0 => {
262 DmaClientBacking::PrivatePoolLowerVtl(LowerVtlMemorySpawner::new(
265 private
266 .allocator(device_name.into())
267 .context("failed to create private allocator")?,
268 self.lower_vtl
269 .as_ref()
270 .ok_or(anyhow::anyhow!(
271 "lower vtl not available on hardware isolated platforms"
272 ))?
273 .clone(),
274 ))
275 }
276 },
277 ClientCreation {
278 allocation_visibility: AllocationVisibility::Private,
279 persistent_allocations: true,
280 shared_spawner: _,
281 private_spawner: None,
282 } => {
283 anyhow::bail!("no sources available for private persistent allocations")
285 }
286 ClientCreation {
287 allocation_visibility: AllocationVisibility::Private,
288 persistent_allocations: false,
289 shared_spawner: _,
290 private_spawner: _,
291 } => match lower_vtl_policy {
292 LowerVtlPermissionPolicy::Any => {
293 DmaClientBacking::LockedMemory(LockedMemorySpawner)
296 }
297 LowerVtlPermissionPolicy::Vtl0 => {
298 DmaClientBacking::LockedMemoryLowerVtl(LowerVtlMemorySpawner::new(
301 LockedMemorySpawner,
302 self.lower_vtl
303 .as_ref()
304 .ok_or(anyhow::anyhow!(
305 "lower vtl not available on hardware isolated platforms"
306 ))?
307 .clone(),
308 ))
309 }
310 },
311 }
312 };
313
314 Ok(Arc::new(OpenhclDmaClient { backing, params }))
315 }
316}
317
318impl OpenhclDmaManager {
319 pub fn new(
322 shared_ranges: &[MemoryRange],
323 private_ranges: &[MemoryRange],
324 vtom: u64,
325 isolation_type: virt::IsolationType,
326 ) -> anyhow::Result<Self> {
327 tracing::info!(
328 ?shared_ranges,
329 ?private_ranges,
330 vtom,
331 ?isolation_type,
332 "create dma manager"
333 );
334
335 let shared_pool = if shared_ranges.is_empty() {
336 None
337 } else {
338 Some(
339 PagePool::new(
340 shared_ranges,
341 HclMapper::new_shared(vtom).context("failed to create hcl mapper")?,
342 )
343 .context("failed to create shared page pool")?,
344 )
345 };
346
347 let private_pool = if private_ranges.is_empty() {
348 None
349 } else {
350 Some(
351 PagePool::new(
352 private_ranges,
353 HclMapper::new_private().context("failed to create hcl mapper")?,
354 )
355 .context("failed to create private page pool")?,
356 )
357 };
358
359 Ok(OpenhclDmaManager {
360 inner: Arc::new(DmaManagerInner {
361 shared_spawner: shared_pool.as_ref().map(|pool| pool.allocator_spawner()),
362 private_spawner: private_pool.as_ref().map(|pool| pool.allocator_spawner()),
363 lower_vtl: if isolation_type.is_hardware_isolated() {
364 None
365 } else {
366 Some(DmaManagerLowerVtl::new().context("failed to create lower vtl")?)
367 },
368 }),
369 shared_pool,
370 private_pool,
371 })
372 }
373
374 pub fn new_client(&self, params: DmaClientParameters) -> anyhow::Result<Arc<OpenhclDmaClient>> {
377 self.inner.new_dma_client(params)
378 }
379
380 pub fn client_spawner(&self) -> DmaClientSpawner {
382 DmaClientSpawner {
383 inner: self.inner.clone(),
384 }
385 }
386
387 pub fn validate_restore(&self) -> anyhow::Result<()> {
389 if let Some(shared_pool) = &self.shared_pool {
392 shared_pool
393 .validate_restore(false)
394 .context("failed to validate restore for shared pool")?
395 }
396
397 if let Some(private_pool) = &self.private_pool {
398 private_pool
399 .validate_restore(false)
400 .context("failed to validate restore for private pool")?
401 }
402
403 Ok(())
404 }
405}
406
407#[derive(Clone)]
409pub struct DmaClientSpawner {
410 inner: Arc<DmaManagerInner>,
411}
412
413impl DmaClientSpawner {
414 pub fn new_client(&self, params: DmaClientParameters) -> anyhow::Result<Arc<OpenhclDmaClient>> {
416 self.inner.new_dma_client(params)
417 }
418}
419
420#[derive(Inspect)]
423#[inspect(tag = "type")]
424enum DmaClientBacking {
425 SharedPool(#[inspect(skip)] PagePoolAllocator),
426 PrivatePool(#[inspect(skip)] PagePoolAllocator),
427 LockedMemory(#[inspect(skip)] LockedMemorySpawner),
428 PrivatePoolLowerVtl(#[inspect(skip)] LowerVtlMemorySpawner<PagePoolAllocator>),
429 LockedMemoryLowerVtl(#[inspect(skip)] LowerVtlMemorySpawner<LockedMemorySpawner>),
430}
431
432impl DmaClientBacking {
433 fn allocate_dma_buffer(
434 &self,
435 total_size: usize,
436 ) -> anyhow::Result<user_driver::memory::MemoryBlock> {
437 match self {
438 DmaClientBacking::SharedPool(allocator) => allocator.allocate_dma_buffer(total_size),
439 DmaClientBacking::PrivatePool(allocator) => allocator.allocate_dma_buffer(total_size),
440 DmaClientBacking::LockedMemory(spawner) => spawner.allocate_dma_buffer(total_size),
441 DmaClientBacking::PrivatePoolLowerVtl(spawner) => {
442 spawner.allocate_dma_buffer(total_size)
443 }
444 DmaClientBacking::LockedMemoryLowerVtl(spawner) => {
445 spawner.allocate_dma_buffer(total_size)
446 }
447 }
448 }
449
450 fn attach_pending_buffers(&self) -> anyhow::Result<Vec<user_driver::memory::MemoryBlock>> {
451 match self {
452 DmaClientBacking::SharedPool(allocator) => allocator.attach_pending_buffers(),
453 DmaClientBacking::PrivatePool(allocator) => allocator.attach_pending_buffers(),
454 DmaClientBacking::LockedMemory(_) => {
455 anyhow::bail!(
456 "attaching pending buffers is not supported with locked memory; \
457 this client type does not maintain a pool of pending allocations. \
458 To use attach_pending_buffers, create a client backed by a shared or private pool."
459 )
460 }
461 DmaClientBacking::PrivatePoolLowerVtl(spawner) => spawner.attach_pending_buffers(),
462 DmaClientBacking::LockedMemoryLowerVtl(spawner) => spawner.attach_pending_buffers(),
463 }
464 }
465}
466
467#[derive(Inspect)]
470pub struct OpenhclDmaClient {
471 backing: DmaClientBacking,
472 params: DmaClientParameters,
473}
474
475impl DmaClient for OpenhclDmaClient {
476 fn allocate_dma_buffer(
477 &self,
478 total_size: usize,
479 ) -> anyhow::Result<user_driver::memory::MemoryBlock> {
480 self.backing.allocate_dma_buffer(total_size)
481 }
482
483 fn attach_pending_buffers(&self) -> anyhow::Result<Vec<user_driver::memory::MemoryBlock>> {
484 self.backing.attach_pending_buffers()
485 }
486}