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}