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}