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}