use core::{alloc::Layout, cmp::PartialOrd, num::NonZeroU64, ptr::NonNull};
use derive_more::{
Add, AddAssign, Debug, Display, Div, From, Into, Mul, MulAssign, Sub, SubAssign,
};
#[derive(
Debug,
Display,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Hash,
From,
Into,
Add,
AddAssign,
Mul,
MulAssign,
Sub,
SubAssign,
Div,
)]
#[debug("{}", format_args!("{_0:#X}"))]
#[display("{}", format_args!("{_0:#X}"))]
pub struct DmaAddr(u64);
impl DmaAddr {
pub fn as_u64(&self) -> u64 {
self.0
}
pub fn checked_add(&self, rhs: u64) -> Option<Self> {
self.0.checked_add(rhs).map(DmaAddr)
}
}
impl PartialEq<u64> for DmaAddr {
fn eq(&self, other: &u64) -> bool {
self.0 == *other
}
}
impl PartialOrd<u64> for DmaAddr {
fn partial_cmp(&self, other: &u64) -> Option<core::cmp::Ordering> {
self.0.partial_cmp(other)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DmaDomainId {
Direct,
Translated(NonZeroU64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DmaConstraints {
pub addr_mask: u64,
pub align: usize,
pub boundary: Option<usize>,
pub max_segment_size: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DmaCoherency {
Coherent,
NonCoherent,
}
impl DmaConstraints {
pub const fn new(addr_mask: u64) -> Self {
Self {
addr_mask,
align: 1,
boundary: None,
max_segment_size: None,
}
}
pub const fn with_align(mut self, align: usize) -> Self {
self.align = if align == 0 { 1 } else { align };
self
}
pub const fn with_boundary(mut self, boundary: usize) -> Self {
self.boundary = Some(if boundary == 0 { 1 } else { boundary });
self
}
pub const fn with_max_segment_size(mut self, max_segment_size: usize) -> Self {
self.max_segment_size = Some(max_segment_size);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DmaDeviceInfo {
domain: DmaDomainId,
coherency: DmaCoherency,
constraints: DmaConstraints,
}
impl DmaDeviceInfo {
pub const fn new(
domain: DmaDomainId,
coherency: DmaCoherency,
constraints: DmaConstraints,
) -> Self {
Self {
domain,
coherency,
constraints,
}
}
pub const fn domain(self) -> DmaDomainId {
self.domain
}
pub const fn coherency(self) -> DmaCoherency {
self.coherency
}
pub const fn constraints(self) -> DmaConstraints {
self.constraints
}
pub const fn with_constraints(self, constraints: DmaConstraints) -> Self {
Self {
constraints,
..self
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DmaDirection {
ToDevice,
FromDevice,
Bidirectional,
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum DmaError {
#[error("DMA allocation failed")]
NoMemory,
#[error("Invalid layout")]
LayoutError(#[from] core::alloc::LayoutError),
#[error("DMA address {addr} does not match device mask {mask:#X}")]
DmaMaskNotMatch { addr: DmaAddr, mask: u64 },
#[error("DMA align mismatch: required={required:#X}, but address={address}")]
AlignMismatch { required: usize, address: DmaAddr },
#[error("DMA segment size {size:#X} exceeds max segment size {max:#X}")]
SegmentTooLarge { size: usize, max: usize },
#[error("DMA address range crosses boundary {boundary:#X}: addr={addr}, size={size:#X}")]
BoundaryCross {
addr: DmaAddr,
size: usize,
boundary: usize,
},
#[error("Null pointer provided for DMA mapping")]
NullPointer,
#[error("Zero-sized buffer cannot be used for DMA")]
ZeroSizedBuffer,
#[error("DMA coherent allocation could not be released and was quarantined")]
CoherentReleaseFailed,
}
pub unsafe trait DmaPod: Copy {}
unsafe impl<T: Copy> DmaPod for T {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DmaAllocHandle {
pub(crate) cpu_addr: NonNull<u8>,
pub(crate) allocation_addr: NonNull<u8>,
pub(crate) dma_addr: DmaAddr,
pub(crate) layout: Layout,
}
impl DmaAllocHandle {
pub unsafe fn new(
cpu_addr: NonNull<u8>,
allocation_addr: NonNull<u8>,
dma_addr: DmaAddr,
layout: Layout,
) -> Self {
Self {
cpu_addr,
allocation_addr,
dma_addr,
layout,
}
}
pub fn size(&self) -> usize {
self.layout.size()
}
pub fn align(&self) -> usize {
self.layout.align()
}
pub fn as_ptr(&self) -> NonNull<u8> {
self.cpu_addr
}
pub fn allocation_ptr(&self) -> NonNull<u8> {
self.allocation_addr
}
pub fn dma_addr(&self) -> DmaAddr {
self.dma_addr
}
pub fn layout(&self) -> Layout {
self.layout
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DmaMapHandle {
pub(crate) cpu_addr: NonNull<u8>,
pub(crate) dma_addr: DmaAddr,
pub(crate) layout: Layout,
pub(crate) bounce_ptr: Option<NonNull<u8>>,
}
impl DmaMapHandle {
pub unsafe fn new(
cpu_addr: NonNull<u8>,
dma_addr: DmaAddr,
layout: Layout,
bounce_ptr: Option<NonNull<u8>>,
) -> Self {
Self {
cpu_addr,
dma_addr,
layout,
bounce_ptr,
}
}
pub fn size(&self) -> usize {
self.layout.size()
}
pub fn align(&self) -> usize {
self.layout.align()
}
pub fn as_ptr(&self) -> NonNull<u8> {
self.cpu_addr
}
pub fn dma_addr(&self) -> DmaAddr {
self.dma_addr
}
pub fn layout(&self) -> Layout {
self.layout
}
pub fn bounce_ptr(&self) -> Option<NonNull<u8>> {
self.bounce_ptr
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coherent_handle_keeps_cpu_alias_and_allocator_address_distinct() {
let alias = NonNull::new(0x8000_usize as *mut u8).unwrap();
let allocation = NonNull::new(0x4000_usize as *mut u8).unwrap();
let layout = Layout::from_size_align(0x1000, 0x1000).unwrap();
let handle = unsafe { DmaAllocHandle::new(alias, allocation, 0x2000_u64.into(), layout) };
assert_eq!(handle.as_ptr(), alias);
assert_eq!(handle.allocation_ptr(), allocation);
assert_eq!(handle.dma_addr(), DmaAddr::from(0x2000_u64));
}
}