Skip to main content

strat9_kernel/dma/
buffer.rs

1use crate::{
2    arch::xshim::PhysAddr,
3    memory::{self, frame::get_meta, phys_to_virt, PhysFrame},
4};
5
6/// A pinned, contiguous DMA buffer.
7///
8/// Allocated from the buddy allocator with `allocate_phys_contiguous`.
9/// The underlying frames are marked with the [`frame_flags::DMA`] flag,
10/// preventing the buddy from recycling them while a transfer is in flight.
11///
12/// The buffer is automatically unpinned on [`Drop`], using RAII to
13/// guarantee cleanup even on panic paths.
14pub struct DmaBuffer {
15    frame: PhysFrame,
16    order: u8,
17    phys: u64,
18    virt: u64,
19    /// Number of 4 KiB frames in this buffer.
20    nframes: usize,
21}
22
23impl DmaBuffer {
24    /// Allocate a DMA buffer large enough for `bytes`, pinned for the
25    /// duration of the transfer.  The backing memory is contiguous in
26    /// physical address space.
27    ///
28    /// The buffer is **not zeroed** (caller must overwrite every byte
29    /// before the first read).
30    pub fn alloc(bytes: usize) -> Result<Self, DmaError> {
31        let nframes = (bytes + 4095) / 4096;
32        let order = nframes.next_power_of_two().trailing_zeros() as u8;
33
34        let frame =
35            crate::sync::with_irqs_disabled(|token| memory::allocate_phys_contiguous(token, order))
36                .map_err(|_| DmaError::Alloc)?;
37
38        let phys = frame.start_address.as_u64();
39
40        // Mark every frame in the range as DMA-pinned so the buddy
41        // allocator will not reuse them until we unpin.
42        for i in 0..(1usize << order) {
43            let meta = get_meta(PhysAddr::new(phys + (i as u64) * 4096));
44            meta.or_flags(memory::frame::frame_flags::DMA);
45        }
46
47        Ok(Self {
48            frame,
49            order,
50            phys,
51            virt: phys_to_virt(phys),
52            nframes: 1usize << order,
53        })
54    }
55
56    /// Physical base address : pass this to hardware DMA engines.
57    #[inline]
58    pub fn dma_addr(&self) -> PhysAddr {
59        PhysAddr::new(self.phys)
60    }
61
62    /// Kernel virtual address (HHDM mapping) for CPU access.
63    #[inline]
64    pub fn virt_addr(&self) -> u64 {
65        self.virt
66    }
67
68    /// Number of bytes allocated (always a multiple of 4096).
69    #[inline]
70    pub fn len_bytes(&self) -> usize {
71        self.nframes * 4096
72    }
73}
74
75impl Drop for DmaBuffer {
76    fn drop(&mut self) {
77        // Clear the DMA flag so the buddy can reuse these frames.
78        for i in 0..self.nframes {
79            let meta = get_meta(PhysAddr::new(self.phys + (i as u64) * 4096));
80            meta.and_flags(!memory::frame::frame_flags::DMA);
81        }
82        crate::sync::with_irqs_disabled(|token| {
83            memory::free_phys_contiguous(token, self.frame, self.order);
84        });
85    }
86}
87
88// SAFETY: DmaBuffer owns a contiguous physical range; the virtual
89// address is derived via the identity-like HHDM mapping and is valid
90// for the lifetime of the buffer.  Only Send is implemented : the
91// hardware (DMA engine) writes into the buffer without CPU synchronisation,
92// making &DmaBuffer across threads unsafe until an explicit fence.
93unsafe impl Send for DmaBuffer {}
94
95// =============================================================================
96// DmaError
97// =============================================================================
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum DmaError {
101    /// Physical memory allocation failed.
102    Alloc,
103}