Skip to main content

strat9_kernel/dma/
buffer.rs

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