Skip to main content

ostd/mm/dma/
dma_stream.rs

1// SPDX-License-Identifier: MPL-2.0
2
3use core::{fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ops::Range};
4
5use super::util::{
6    alloc_kva, cvm_need_private_protection, prepare_dma, split_daddr, unprepare_dma,
7};
8use crate::{
9    arch::{irq, mm::can_sync_dma},
10    error::Error,
11    mm::{
12        Daddr, FrameAllocOptions, HasDaddr, HasPaddr, HasPaddrRange, HasSize, Infallible,
13        PAGE_SIZE, Paddr, Split, USegment, VmReader, VmWriter,
14        io::util::{HasVmReaderWriter, VmReaderWriterResult},
15        kspace::kvirt_area::KVirtArea,
16        paddr_to_vaddr,
17    },
18};
19
20/// [`DmaDirection`] limits the data flow direction of [`DmaStream`] and
21/// prevents users from reading and writing to [`DmaStream`] unexpectedly.
22pub trait DmaDirection: 'static + Debug + private::Sealed {
23    /// Whether the CPU can read data from the device.
24    const CAN_READ_FROM_DEVICE: bool;
25    /// Whether the CPU can write data to the device.
26    const CAN_WRITE_TO_DEVICE: bool;
27}
28
29mod private {
30    /// To avoid users implement `DmaDirection` and triggers unreachable code in
31    /// functions like [`crate::arch::mm::sync_dma_range`], or bypasses checks
32    /// in [`crate::mm::io::util::HasVmReaderWriter`].
33    pub trait Sealed {}
34}
35
36/// Data flows to the device.
37///
38/// From the perspective of the kernel, this memory region is writable.
39#[derive(Debug)]
40pub enum ToDevice {}
41
42impl private::Sealed for ToDevice {}
43impl DmaDirection for ToDevice {
44    const CAN_READ_FROM_DEVICE: bool = false;
45    const CAN_WRITE_TO_DEVICE: bool = true;
46}
47
48/// Data flows from the device.
49///
50/// From the perspective of the kernel, this memory region is read-only.
51#[derive(Debug)]
52pub enum FromDevice {}
53
54impl private::Sealed for FromDevice {}
55impl DmaDirection for FromDevice {
56    const CAN_READ_FROM_DEVICE: bool = true;
57    const CAN_WRITE_TO_DEVICE: bool = false;
58}
59
60/// Data flows both from and to the device.
61#[derive(Debug)]
62pub enum FromAndToDevice {}
63
64impl private::Sealed for FromAndToDevice {}
65impl DmaDirection for FromAndToDevice {
66    const CAN_READ_FROM_DEVICE: bool = true;
67    const CAN_WRITE_TO_DEVICE: bool = true;
68}
69
70/// A DMA memory object with streaming access.
71///
72/// The kernel must synchronize the data by [`sync_from_device`]/[`sync_to_device`]
73/// when interacting with the device.
74///
75/// For whether the associated methods can be used in IRQs, refer to
76/// [module-level docs](crate::mm::dma#usage-in-irqs).
77///
78/// [`sync_from_device`]: DmaStream::sync_from_device
79/// [`sync_to_device`]: DmaStream::sync_to_device
80#[derive(Debug)]
81pub struct DmaStream<D: DmaDirection = FromAndToDevice> {
82    inner: Inner,
83    map_daddr: Option<Daddr>,
84    is_cache_coherent: bool,
85    _phantom: PhantomData<D>,
86}
87
88#[derive(Debug)]
89enum Inner {
90    Segment(USegment),
91    Kva(KVirtArea, Paddr),
92    Both(KVirtArea, Paddr, USegment),
93}
94
95impl<D: DmaDirection> DmaStream<D> {
96    /// Allocates a region of physical memory for streaming DMA access.
97    ///
98    /// The memory of the newly-allocated DMA buffer is initialized to zeros.
99    /// This method is only available when `D` is [`ToDevice`] or
100    /// [`FromAndToDevice`], as zeroing requires write access to the buffer.
101    ///
102    /// The `is_cache_coherent` argument specifies whether the target device
103    /// that the DMA mapping is prepared for can access the main memory in a
104    /// CPU cache coherent way or not.
105    ///
106    /// This method [requires](crate::mm::dma#usage-in-irqs) the caller to
107    /// have IRQs enabled.
108    ///
109    /// # Comparison with [`DmaStream::map`]
110    ///
111    /// This method is semantically equivalent to allocating a [`USegment`] via
112    /// [`FrameAllocOptions::alloc_segment`] and then mapping it with
113    /// [`DmaStream::map`]. However, [`DmaStream::alloc`] combines these two
114    /// operations and can be more efficient in certain scenarios, particularly
115    /// in confidential VMs, where the overhead of bounce buffers can be
116    /// avoided.
117    pub fn alloc(nframes: usize, is_cache_coherent: bool) -> Result<Self, Error> {
118        const { assert!(D::CAN_WRITE_TO_DEVICE) };
119
120        Self::alloc_uninit(nframes, is_cache_coherent).and_then(|dma| {
121            dma.writer()?.fill_zeros(dma.size());
122            Ok(dma)
123        })
124    }
125
126    /// Allocates a region of physical memory for streaming DMA access
127    /// without initialization.
128    ///
129    /// This method is the same as [`DmaStream::alloc`]
130    /// except that it skips zeroing the memory of newly-allocated DMA region.
131    ///
132    /// This method [requires](crate::mm::dma#usage-in-irqs) the caller to
133    /// have IRQs enabled.
134    pub fn alloc_uninit(nframes: usize, is_cache_coherent: bool) -> Result<Self, Error> {
135        debug_assert!(irq::is_local_enabled());
136
137        let cvm = cvm_need_private_protection();
138
139        let (inner, paddr_range) = if (can_sync_dma() || is_cache_coherent) && !cvm {
140            let segment: USegment = FrameAllocOptions::new()
141                .zeroed(false)
142                .alloc_segment(nframes)?
143                .into();
144            let paddr_range = segment.paddr_range();
145
146            (Inner::Segment(segment), paddr_range)
147        } else {
148            let (kva, paddr) = alloc_kva(nframes, can_sync_dma() || is_cache_coherent)?;
149
150            (Inner::Kva(kva, paddr), paddr..paddr + nframes * PAGE_SIZE)
151        };
152
153        // SAFETY: The physical address range is untyped DMA memory before `drop`.
154        let map_daddr = unsafe { prepare_dma(&paddr_range) };
155
156        Ok(Self {
157            inner,
158            map_daddr,
159            is_cache_coherent,
160            _phantom: PhantomData,
161        })
162    }
163
164    /// Establishes DMA stream mapping for a given [`USegment`].
165    ///
166    /// The `is_cache_coherent` argument specifies whether the target device
167    /// that the DMA mapping is prepared for can access the main memory in a
168    /// CPU cache coherent way or not.
169    ///
170    /// This method [requires](crate::mm::dma#usage-in-irqs) the caller to
171    /// have IRQs enabled.
172    pub fn map(segment: USegment, is_cache_coherent: bool) -> Result<Self, Error> {
173        debug_assert!(irq::is_local_enabled());
174
175        let cvm = cvm_need_private_protection();
176        let size = segment.size();
177
178        let (inner, paddr) = if (can_sync_dma() || is_cache_coherent) && !cvm {
179            let paddr = segment.paddr();
180
181            (Inner::Segment(segment), paddr)
182        } else {
183            let (kva, paddr) = alloc_kva(size / PAGE_SIZE, can_sync_dma() || is_cache_coherent)?;
184
185            (Inner::Both(kva, paddr, segment), paddr)
186        };
187
188        let paddr_range = paddr..paddr + size;
189
190        // SAFETY: The physical address range is untyped DMA memory before `drop`.
191        let map_daddr = unsafe { prepare_dma(&paddr_range) };
192
193        Ok(Self {
194            inner,
195            map_daddr,
196            is_cache_coherent,
197            _phantom: PhantomData,
198        })
199    }
200
201    /// Synchronizes the streaming DMA mapping data from the device.
202    ///
203    /// This method should be called when the data of the streaming DMA mapping
204    /// has been updated by the device side. Before the CPU side starts to read
205    /// (e.g., using [`read_bytes`]), it must call the [`Self::sync_from_device`]
206    /// method first.
207    ///
208    /// [`read_bytes`]: crate::mm::VmIo::read_bytes
209    pub fn sync_from_device(&self, byte_range: Range<usize>) -> Result<(), Error> {
210        const { assert!(D::CAN_READ_FROM_DEVICE) };
211
212        self.sync_impl(byte_range, true)
213    }
214
215    /// Synchronizes the streaming DMA mapping data to the device.
216    ///
217    /// This method should be called when the data of the streaming DMA mapping
218    /// has been updated by the CPU side (e.g., using [`write_bytes`]). Before
219    /// the CPU side notifies the device side to read, it must call the
220    /// [`Self::sync_to_device`] method first.
221    ///
222    /// [`write_bytes`]: crate::mm::VmIo::write_bytes
223    pub fn sync_to_device(&self, byte_range: Range<usize>) -> Result<(), Error> {
224        const { assert!(D::CAN_WRITE_TO_DEVICE) };
225
226        self.sync_impl(byte_range, false)
227    }
228
229    fn sync_impl(&self, byte_range: Range<usize>, is_from_device: bool) -> Result<(), Error> {
230        let size = self.size();
231        if byte_range.start > byte_range.end || byte_range.start > size {
232            return Err(Error::InvalidArgs);
233        }
234
235        if !is_from_device && let Inner::Both(kva, _, seg) = &self.inner {
236            self.sync_via_copying(byte_range.clone(), false, seg, kva);
237        }
238
239        // SAFETY: We've checked that the range is inbound.
240        unsafe { self.sync_cache(byte_range.clone()) };
241
242        if is_from_device && let Inner::Both(kva, _, seg) = &self.inner {
243            self.sync_via_copying(byte_range, true, seg, kva);
244        }
245
246        Ok(())
247    }
248
249    /// # Safety
250    ///
251    /// The caller must ensure that `byte_range` is inbound.
252    unsafe fn sync_cache(&self, byte_range: Range<usize>) {
253        if self.is_cache_coherent {
254            return;
255        }
256
257        let va_range = match &self.inner {
258            Inner::Segment(segment) => {
259                let pa_range = segment.paddr_range();
260                paddr_to_vaddr(pa_range.start)..paddr_to_vaddr(pa_range.end)
261            }
262            Inner::Kva(kva, _) | Inner::Both(kva, _, _) => {
263                if !can_sync_dma() {
264                    // The KVA is mapped as uncachable.
265                    return;
266                }
267                kva.range()
268            }
269        };
270        let range = va_range.start + byte_range.start..va_range.start + byte_range.end;
271
272        // SAFETY:
273        // 1. The range is inbound, so the virtual address range and the DMA
274        //    direction correspond to a DMA region (they're part of `self`).
275        // 2. `can_sync_dma()` is either checked above (for `Inner::Kva` and
276        //    `Inner::Both`) or when constructing `self` (for `Inner::Segment`).
277        unsafe { crate::arch::mm::sync_dma_range::<D>(range) };
278    }
279
280    fn sync_via_copying(
281        &self,
282        byte_range: Range<usize>,
283        is_from_device: bool,
284        seg: &USegment,
285        kva: &KVirtArea,
286    ) {
287        let skip = byte_range.start;
288        let limit = byte_range.len();
289
290        let (mut reader, mut writer) = if is_from_device {
291            // SAFETY:
292            //  - The memory range points to untyped memory.
293            //  - The KVA is alive in this scope.
294            //  - Using `VmReader` and `VmWriter` is the only way to access the KVA.
295            let kva_reader =
296                unsafe { VmReader::from_kernel_space(kva.start() as *const u8, kva.size()) };
297
298            (kva_reader, seg.writer())
299        } else {
300            // SAFETY:
301            //  - The memory range points to untyped memory.
302            //  - The KVA is alive in this scope.
303            //  - Using `VmReader` and `VmWriter` is the only way to access the KVA.
304            let kva_writer =
305                unsafe { VmWriter::from_kernel_space(kva.start() as *mut u8, kva.size()) };
306
307            (seg.reader(), kva_writer)
308        };
309
310        writer
311            .skip(skip)
312            .limit(limit)
313            .write(reader.skip(skip).limit(limit));
314    }
315}
316
317impl<D: DmaDirection> Split for DmaStream<D> {
318    fn split(self, offset: usize) -> (Self, Self) {
319        assert!(offset.is_multiple_of(PAGE_SIZE));
320        assert!(0 < offset && offset < self.size());
321
322        let (inner, map_daddr, is_cache_coherent) = {
323            let this = ManuallyDrop::new(self);
324            (
325                // SAFETY: `this.inner` will never be used or dropped later.
326                unsafe { core::ptr::read(&this.inner as *const Inner) },
327                this.map_daddr,
328                this.is_cache_coherent,
329            )
330        };
331
332        let (inner1, inner2) = match inner {
333            Inner::Segment(segment) => {
334                let (s1, s2) = segment.split(offset);
335                (Inner::Segment(s1), Inner::Segment(s2))
336            }
337            Inner::Kva(kva, paddr) => {
338                let (kva1, kva2) = kva.split(offset);
339                let (paddr1, paddr2) = (paddr, paddr + offset);
340                (Inner::Kva(kva1, paddr1), Inner::Kva(kva2, paddr2))
341            }
342            Inner::Both(kva, paddr, segment) => {
343                let (kva1, kva2) = kva.split(offset);
344                let (paddr1, paddr2) = (paddr, paddr + offset);
345                let (s1, s2) = segment.split(offset);
346                (Inner::Both(kva1, paddr1, s1), Inner::Both(kva2, paddr2, s2))
347            }
348        };
349
350        let (daddr1, daddr2) = split_daddr(map_daddr, offset);
351
352        (
353            Self {
354                inner: inner1,
355                map_daddr: daddr1,
356                is_cache_coherent,
357                _phantom: PhantomData,
358            },
359            Self {
360                inner: inner2,
361                map_daddr: daddr2,
362                is_cache_coherent,
363                _phantom: PhantomData,
364            },
365        )
366    }
367}
368
369impl<D: DmaDirection> Drop for DmaStream<D> {
370    fn drop(&mut self) {
371        // SAFETY: The physical address range was prepared in `map`.
372        unsafe { unprepare_dma(&self.paddr_range(), self.map_daddr) };
373    }
374}
375
376impl<D: DmaDirection> HasPaddr for DmaStream<D> {
377    fn paddr(&self) -> Paddr {
378        match &self.inner {
379            Inner::Segment(segment) => segment.paddr(),
380            Inner::Kva(_, paddr) | Inner::Both(_, paddr, _) => *paddr, // the mapped PA, not the buffer's PA
381        }
382    }
383}
384
385impl<D: DmaDirection> HasDaddr for DmaStream<D> {
386    fn daddr(&self) -> Daddr {
387        self.map_daddr.unwrap_or_else(|| self.paddr() as Daddr)
388    }
389}
390
391impl<D: DmaDirection> HasSize for DmaStream<D> {
392    fn size(&self) -> usize {
393        match &self.inner {
394            Inner::Segment(segment) => segment.size(),
395            Inner::Kva(kva, _) => kva.size(),
396            Inner::Both(kva, _, segment) => {
397                debug_assert_eq!(kva.size(), segment.size());
398                kva.size()
399            }
400        }
401    }
402}
403
404impl<D: DmaDirection> HasVmReaderWriter for DmaStream<D> {
405    type Types = VmReaderWriterResult;
406
407    fn reader(&self) -> Result<VmReader<'_, Infallible>, Error> {
408        if !D::CAN_READ_FROM_DEVICE {
409            return Err(Error::AccessDenied);
410        }
411        match &self.inner {
412            Inner::Segment(seg) | Inner::Both(_, _, seg) => Ok(seg.reader()),
413            Inner::Kva(kva, _) => {
414                // SAFETY:
415                //  - Although the memory range points to typed memory, the range is for DMA
416                //    and the access is not by linear mapping.
417                //  - The KVA is alive during the lifetime `'_`.
418                //  - Using `VmReader` and `VmWriter` is the only way to access the KVA.
419                unsafe {
420                    Ok(VmReader::from_kernel_space(
421                        kva.start() as *const u8,
422                        kva.size(),
423                    ))
424                }
425            }
426        }
427    }
428
429    fn writer(&self) -> Result<VmWriter<'_, Infallible>, Error> {
430        if !D::CAN_WRITE_TO_DEVICE {
431            return Err(Error::AccessDenied);
432        }
433        match &self.inner {
434            Inner::Segment(seg) | Inner::Both(_, _, seg) => Ok(seg.writer()),
435            Inner::Kva(kva, _) => {
436                // SAFETY:
437                //  - Although the memory range points to typed memory, the range is for DMA
438                //    and the access is not by linear mapping.
439                //  - The KVA is alive during the lifetime `'_`.
440                //  - Using `VmReader` and `VmWriter` is the only way to access the KVA.
441                unsafe {
442                    Ok(VmWriter::from_kernel_space(
443                        kva.start() as *mut u8,
444                        kva.size(),
445                    ))
446                }
447            }
448        }
449    }
450}