Skip to main content

ixgbe_driver/
memory.rs

1//! Memory management for the ixgbe driver.
2//!
3//! This module provides memory pool management and DMA allocation utilities for the
4//! ixgbe driver. It includes:
5//!
6//! - [`MemPool`]: A fixed-size memory pool for efficient packet buffer allocation
7//! - [`Packet`]: A packet buffer with automatic memory management
8//! - [`alloc_pkt`]: Convenience function for allocating packets from a memory pool
9//!
10//! # Memory Pool
11//!
12//! The memory pool pre-allocates a fixed number of equally-sized buffers from DMA-capable
13//! memory. This design minimizes allocation overhead and ensures all packet buffers are
14//! accessible by the NIC hardware.
15//!
16//! # Example
17//!
18//! ```rust,ignore
19//! use ixgbe_driver::memory::MemPool;
20//!
21//! // Create a pool with 4096 entries, each 2048 bytes
22//! let pool = MemPool::allocate::<MyHal>(4096, 2048)?;
23//!
24//! // Allocate a packet from the pool
25//! let packet = alloc_pkt(&pool, 1500)?;
26//! ```
27
28use core::fmt::Debug;
29use core::ops::{Deref, DerefMut};
30use core::ptr::NonNull;
31use core::{cell::RefCell, marker::PhantomData};
32
33use crate::hal::IxgbeHal;
34use crate::{IxgbeError, IxgbeResult};
35use alloc::sync::Arc;
36use alloc::vec::Vec;
37use alloc::{fmt, slice};
38
39/// Physical address in system memory.
40///
41/// This type represents a physical memory address that can be accessed by hardware devices
42/// via DMA.
43pub type PhysAddr = usize;
44/// Virtual address in system memory.
45///
46/// This type represents a virtual memory address that is used by the CPU/Driver to access memory.
47pub type VirtAddr = usize;
48
49const HUGE_PAGE_BITS: u32 = 21;
50const HUGE_PAGE_SIZE: usize = 1 << HUGE_PAGE_BITS;
51
52// this differs from upstream ixy as our packet metadata is stored outside of the actual packet data
53// which results in a different alignment requirement
54/// Headroom reserved at the start of each packet buffer.
55///
56/// This space can be used to prepend headers (e.g., VLAN, Ethernet, IP) without
57/// needing to reallocate or copy the packet data.
58pub const PACKET_HEADROOM: usize = 32;
59
60/// A memory pool for efficient DMA-capable buffer allocation.
61///
62/// The memory pool pre-allocates a fixed number of equally-sized buffers from
63/// DMA-capable memory. This design ensures that:
64///
65/// - All buffers are physically contiguous and accessible by the NIC
66/// - Allocation is fast (O(1) simple stack pop)
67/// - Memory fragmentation is avoided
68///
69/// # Thread Safety
70///
71/// The memory pool uses interior mutability via `RefCell` and can be safely
72/// shared between threads.
73///
74/// # Example
75///
76/// ```rust,ignore
77/// use ixgbe_driver::memory::MemPool;
78///
79/// // Create a pool with 4096 entries of 2048 bytes each
80/// let pool = MemPool::allocate::<MyHal>(4096, 2048)?;
81///
82/// // Get the entry size
83/// assert_eq!(pool.entry_size(), 2048);
84/// ```
85pub struct MemPool {
86    base_addr: *mut u8,
87    num_entries: usize,
88    entry_size: usize,
89    phys_addr: Vec<usize>,
90    pub(crate) free_stack: RefCell<Vec<usize>>,
91}
92
93impl MemPool {
94    /// Allocates a new memory pool.
95    ///
96    /// Creates a memory pool with the specified number of entries, each of the
97    /// given size. The entry size must divide the huge page size (2MB) evenly.
98    ///
99    /// # Arguments
100    ///
101    /// * `entries` - Number of buffer entries in the pool
102    /// * `size` - Size of each entry in bytes (0 defaults to 2048)
103    ///
104    /// # Returns
105    ///
106    /// An `Arc`-wrapped `MemPool` that can be shared across packet buffers.
107    ///
108    /// # Errors
109    ///
110    /// - [`IxgbeError::PageNotAligned`] - If `size` is not a divisor of the page size
111    /// - [`IxgbeError::NoMemory`] - If DMA allocation fails
112    ///
113    /// # Panics
114    ///
115    /// Panics if `size` is not a divisor of the huge page size (2MB).
116    pub fn allocate<H: IxgbeHal>(entries: usize, size: usize) -> IxgbeResult<Arc<MemPool>> {
117        let entry_size = match size {
118            0 => 2048,
119            x => x,
120        };
121
122        if HUGE_PAGE_SIZE % entry_size != 0 {
123            error!("entry size must be a divisor of the page size");
124            return Err(IxgbeError::PageNotAligned);
125        }
126
127        let dma = Dma::<u8, H>::allocate(entries * entry_size, false)?;
128        let mut phys_addr = Vec::with_capacity(entries);
129
130        for i in 0..entries {
131            phys_addr.push(unsafe {
132                H::mmio_virt_to_phys(
133                    NonNull::new(dma.virt.add(i * entry_size)).unwrap(),
134                    entry_size,
135                )
136            })
137        }
138
139        let pool = MemPool {
140            base_addr: dma.virt,
141            num_entries: entries,
142            entry_size,
143            phys_addr,
144            free_stack: RefCell::new(Vec::with_capacity(entries)),
145        };
146
147        let pool = Arc::new(pool);
148        pool.free_stack.borrow_mut().extend(0..entries);
149
150        Ok(pool)
151    }
152
153    /// Returns the position of a free buffer in the memory pool, or [`None`] if the pool is empty.
154    pub(crate) fn alloc_buf(&self) -> Option<usize> {
155        self.free_stack.borrow_mut().pop()
156    }
157
158    /// Marks a buffer in the memory pool as free.
159    pub(crate) fn free_buf(&self, id: usize) {
160        assert!(
161            id < self.num_entries,
162            "buffer outside of memory pool, id: {id}"
163        );
164
165        let mut free_stack = self.free_stack.borrow_mut();
166        if free_stack.contains(&id) {
167            panic!("free buf: buffer already free");
168        }
169
170        free_stack.push(id);
171    }
172
173    /// Returns the size (in bytes) of each entry in the pool.
174    pub fn entry_size(&self) -> usize {
175        self.entry_size
176    }
177
178    /// Returns the virtual address of a buffer from the memory pool.
179    pub(crate) fn get_virt_addr(&self, id: usize) -> *mut u8 {
180        assert!(
181            id < self.num_entries,
182            "buffer outside of memory pool, id: {id}"
183        );
184
185        unsafe { self.base_addr.add(id * self.entry_size) }
186    }
187
188    /// Returns the physical address of a buffer from the memory pool.
189    ///
190    /// This address can be passed to the NIC hardware for DMA operations.
191    pub fn get_phys_addr(&self, id: usize) -> usize {
192        self.phys_addr[id]
193    }
194}
195
196/// DMA-allocated memory block.
197///
198/// Represents a block of physically contiguous memory allocated for DMA operations.
199/// This is a low-level type used internally for descriptor ring allocation.
200pub struct Dma<T, H: IxgbeHal> {
201    pub virt: *mut T,
202    pub phys: usize,
203    _marker: PhantomData<H>,
204}
205
206impl<T, H: IxgbeHal> Dma<T, H> {
207    /// Allocates a new DMA memory block.
208    ///
209    /// # Arguments
210    ///
211    /// * `size` - Size of the allocation in bytes
212    /// * `_require_contiguous` - Whether memory must be contiguous (currently unused)
213    ///
214    /// # Errors
215    ///
216    /// Returns [`IxgbeError::NoMemory`] if the allocation fails.
217    pub fn allocate(size: usize, _require_contiguous: bool) -> IxgbeResult<Dma<T, H>> {
218        // let size = if size % HUGE_PAGE_SIZE != 0 {
219        //     ((size >> HUGE_PAGE_BITS) + 1) << HUGE_PAGE_BITS
220        // } else {
221        //     size
222        // };
223        // let size = if size < 0x1000 { 0x1000 } else { size };
224        // let (pa, va) = H::dma_alloc(size / 0x1000, crate::BufferDirection::Both);
225        let (pa, va) = H::dma_alloc(size);
226        info!(
227            "allocated DMA memory @pa: {:#x}, va: {:#x}, size: {:#x}",
228            pa,
229            va.as_ptr() as usize,
230            size
231        );
232        Ok(Dma::<T, H> {
233            virt: va.as_ptr() as *mut T,
234            phys: pa,
235            _marker: PhantomData,
236        })
237    }
238}
239
240/// A packet buffer with automatic memory management.
241///
242/// `Packet` represents a buffer that was allocated from a [`MemPool`]. When the
243/// packet is dropped, the buffer is automatically returned to the pool for reuse.
244///
245/// # Data Access
246///
247/// The packet implements `Deref` and `DerefMut` to `[u8]`, allowing direct access
248/// to the packet data as a byte slice.
249///
250/// # Cloning
251///
252/// Cloning a packet creates a deep copy by allocating a new buffer from the pool
253/// and copying the data. The original packet remains valid.
254///
255/// # Example
256///
257/// ```rust,ignore
258/// use ixgbe_driver::memory::alloc_pkt;
259///
260/// let packet = alloc_pkt(&pool, 1500)?;
261///
262/// // Access data
263/// let data: &[u8] = &packet;
264///
265/// // Modify data
266/// packet.as_mut_bytes()[0] = 0xFF;
267///
268/// // Get addresses for DMA
269/// let phys_addr = packet.get_phys_addr();
270/// let virt_addr = packet.get_virt_addr();
271/// ```
272pub struct Packet {
273    pub(crate) addr_virt: NonNull<u8>,
274    pub(crate) addr_phys: usize,
275    pub(crate) len: usize,
276    pub(crate) pool: Arc<MemPool>,
277    pub(crate) pool_entry: usize,
278}
279
280impl Clone for Packet {
281    fn clone(&self) -> Self {
282        let mut p = alloc_pkt(&self.pool, self.len).expect("no buffer available");
283        p.clone_from_slice(self);
284
285        p
286    }
287}
288
289impl Deref for Packet {
290    type Target = [u8];
291
292    fn deref(&self) -> &[u8] {
293        unsafe { slice::from_raw_parts(self.addr_virt.as_ptr(), self.len) }
294    }
295}
296
297impl DerefMut for Packet {
298    fn deref_mut(&mut self) -> &mut [u8] {
299        unsafe { slice::from_raw_parts_mut(self.addr_virt.as_ptr(), self.len) }
300    }
301}
302
303impl Debug for Packet {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        (**self).fmt(f)
306    }
307}
308
309impl Drop for Packet {
310    fn drop(&mut self) {
311        self.pool.free_buf(self.pool_entry);
312    }
313}
314
315impl Packet {
316    /// Creates a new packet from raw components.
317    ///
318    /// # Safety
319    ///
320    /// The caller must ensure that:
321    /// - `addr_virt` points to valid memory
322    /// - `addr_phys` is the correct physical address for `addr_virt`
323    /// - The memory was allocated from `pool` at entry `pool_entry`
324    /// - `len` does not exceed the allocated buffer size
325    pub(crate) unsafe fn new(
326        addr_virt: *mut u8,
327        addr_phys: usize,
328        len: usize,
329        pool: Arc<MemPool>,
330        pool_entry: usize,
331    ) -> Packet {
332        Packet {
333            addr_virt: NonNull::new_unchecked(addr_virt),
334            addr_phys,
335            len,
336            pool,
337            pool_entry,
338        }
339    }
340
341    /// Returns the virtual address of the packet data.
342    pub fn get_virt_addr(&self) -> *mut u8 {
343        self.addr_virt.as_ptr()
344    }
345
346    /// Returns the physical address of the packet data.
347    ///
348    /// This address is used by the NIC hardware for DMA operations.
349    pub fn get_phys_addr(&self) -> usize {
350        self.addr_phys
351    }
352
353    /// Returns the packet data as a byte slice.
354    pub fn as_bytes(&self) -> &[u8] {
355        unsafe { slice::from_raw_parts(self.addr_virt.as_ptr(), self.len) }
356    }
357
358    /// Returns the packet data as a mutable byte slice.
359    pub fn as_mut_bytes(&mut self) -> &mut [u8] {
360        unsafe { slice::from_raw_parts_mut(self.addr_virt.as_ptr(), self.len) }
361    }
362
363    /// Returns a mutable slice to the headroom of the packet.
364    ///
365    /// The `len` parameter controls how much of the headroom is returned.
366    ///
367    /// # Panics
368    ///
369    /// Panics if `len` is greater than [`PACKET_HEADROOM`]
370    pub fn headroom_mut(&mut self, len: usize) -> &mut [u8] {
371        assert!(len <= PACKET_HEADROOM);
372        unsafe { slice::from_raw_parts_mut(self.addr_virt.as_ptr().sub(len), len) }
373    }
374
375    #[cfg(target_arch = "x86_64")]
376    #[inline(always)]
377    pub(crate) fn prefrtch(&self, hint: Prefetch) {
378        if core_detect::is_x86_feature_detected!("sse") {
379            let addr = self.get_virt_addr() as *const _;
380            unsafe {
381                use core::arch::x86_64;
382                match hint {
383                    Prefetch::Time0 => x86_64::_mm_prefetch(addr, x86_64::_MM_HINT_T0),
384                    Prefetch::Time1 => x86_64::_mm_prefetch(addr, x86_64::_MM_HINT_T1),
385                    Prefetch::Time2 => x86_64::_mm_prefetch(addr, x86_64::_MM_HINT_T2),
386                    Prefetch::NonTemporal => x86_64::_mm_prefetch(addr, x86_64::_MM_HINT_NTA),
387                }
388            }
389        }
390    }
391}
392
393/// Allocates a packet from the memory pool.
394///
395/// Attempts to allocate a packet buffer of the specified size from the pool.
396/// The allocation will fail if:
397///
398/// - The pool is exhausted (no free buffers)
399/// - The requested size exceeds the available space after reserving headroom
400///
401/// # Arguments
402///
403/// * `pool` - The memory pool to allocate from
404/// * `size` - Desired packet data size in bytes
405///
406/// # Returns
407///
408/// `Some(Packet)` if allocation succeeded, `None` otherwise.
409///
410/// # Example
411///
412/// ```rust,ignore
413/// use ixgbe_driver::memory::alloc_pkt;
414///
415/// if let Some(packet) = alloc_pkt(&pool, 1500) {
416///     // Use the packet
417/// } else {
418///     // Handle allocation failure
419/// }
420/// ```
421pub fn alloc_pkt(pool: &Arc<MemPool>, size: usize) -> Option<Packet> {
422    if size > pool.entry_size - PACKET_HEADROOM {
423        return None;
424    }
425
426    pool.alloc_buf().map(|id| unsafe {
427        Packet::new(
428            pool.get_virt_addr(id).add(PACKET_HEADROOM),
429            pool.get_phys_addr(id) + PACKET_HEADROOM,
430            size,
431            Arc::clone(pool),
432            id,
433        )
434    })
435}
436
437/// CPU cache prefetch hints for x86_64 SSE instructions.
438///
439/// These hints control how data is prefetched into the CPU cache hierarchy.
440/// Different hints are appropriate for different access patterns.
441///
442/// The `prefetch` functions are only available on x86_64 when SSE is supported.
443#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
444pub enum Prefetch {
445    /// Corresponds to _MM_HINT_T0 on x86 sse.
446    ///
447    /// Fetch data into all cache levels.
448    Time0,
449
450    /// Corresponds to _MM_HINT_T1 on x86 sse.
451    ///
452    /// Fetch data into L2 cache (not L1).
453    Time1,
454
455    /// Corresponds to _MM_HINT_T2 on x86 sse.
456    ///
457    /// Fetch data into L3 cache (not L2 or L1).
458    Time2,
459
460    /// Corresponds to _MM_HINT_NTA on x86 sse.
461    ///
462    /// Non-temporal fetch - data is not expected to be reused.
463    NonTemporal,
464}
465
466unsafe impl Sync for MemPool {}
467unsafe impl Send for MemPool {}
468
469#[cfg(test)]
470mod tests {
471    use super::*;
472
473    #[test]
474    fn test_constants() {
475        assert_eq!(PACKET_HEADROOM, 32);
476        assert_eq!(HUGE_PAGE_BITS, 21);
477        assert_eq!(HUGE_PAGE_SIZE, 1 << 21);
478        assert_eq!(HUGE_PAGE_SIZE, 0x200000);
479    }
480
481    #[test]
482    fn test_prefetch_ord() {
483        assert!(Prefetch::Time0 < Prefetch::Time1);
484        assert!(Prefetch::Time1 < Prefetch::Time2);
485        assert!(Prefetch::Time2 < Prefetch::NonTemporal);
486    }
487
488    #[test]
489    fn test_prefetch_eq() {
490        assert_eq!(Prefetch::Time0, Prefetch::Time0);
491        assert_ne!(Prefetch::Time0, Prefetch::Time1);
492    }
493
494    #[test]
495    fn test_prefetch_copy() {
496        let hint = Prefetch::Time0;
497        let copied = hint;
498        assert_eq!(hint, copied);
499    }
500
501    #[test]
502    fn test_mempool_allocation_alignment() {
503        // Test that various entry sizes divide the huge page size evenly
504        let valid_sizes = [
505            2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288,
506        ];
507        for &size in &valid_sizes {
508            assert_eq!(
509                HUGE_PAGE_SIZE % size,
510                0,
511                "Size {} should divide page size",
512                size
513            );
514        }
515    }
516
517    #[test]
518    fn test_mempool_invalid_alignment() {
519        // Test that invalid sizes do not divide the huge page size evenly
520        let invalid_sizes = [100, 1536, 3000, 5000];
521        for &size in &invalid_sizes {
522            assert_ne!(
523                HUGE_PAGE_SIZE % size,
524                0,
525                "Size {} should not divide page size evenly",
526                size
527            );
528        }
529    }
530
531    #[test]
532    fn test_mempool_entry_size_default() {
533        // Test that default entry size is 2048
534        let size = 0;
535        let entry_size = match size {
536            0 => 2048,
537            x => x,
538        };
539        assert_eq!(entry_size, 2048);
540    }
541}