use crate::{
bindings, build_assert, device,
device::{Bound, Core},
error::{to_result, Result},
prelude::*,
sync::aref::ARef,
transmute::{AsBytes, FromBytes},
};
use core::ptr::NonNull;
pub type DmaAddress = bindings::dma_addr_t;
#[cfg_attr(CONFIG_PCI, doc = "* [`pci::Device`](kernel::pci::Device)")]
pub trait Device: AsRef<device::Device<Core>> {
unsafe fn dma_set_mask(&self, mask: DmaMask) -> Result {
to_result(unsafe { bindings::dma_set_mask(self.as_ref().as_raw(), mask.value()) })
}
unsafe fn dma_set_coherent_mask(&self, mask: DmaMask) -> Result {
to_result(unsafe { bindings::dma_set_coherent_mask(self.as_ref().as_raw(), mask.value()) })
}
unsafe fn dma_set_mask_and_coherent(&self, mask: DmaMask) -> Result {
to_result(unsafe {
bindings::dma_set_mask_and_coherent(self.as_ref().as_raw(), mask.value())
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DmaMask(u64);
impl DmaMask {
#[inline]
pub const fn new<const N: u32>() -> Self {
let Ok(mask) = Self::try_new(N) else {
build_error!("Invalid DMA Mask.");
};
mask
}
#[inline]
pub const fn try_new(n: u32) -> Result<Self> {
Ok(Self(match n {
0 => 0,
1..=64 => u64::MAX >> (64 - n),
_ => return Err(EINVAL),
}))
}
#[inline]
pub const fn value(&self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, PartialEq)]
#[repr(transparent)]
pub struct Attrs(u32);
impl Attrs {
pub(crate) fn as_raw(self) -> crate::ffi::c_ulong {
self.0 as crate::ffi::c_ulong
}
pub fn contains(self, flags: Attrs) -> bool {
(self & flags) == flags
}
}
impl core::ops::BitOr for Attrs {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
Self(self.0 | rhs.0)
}
}
impl core::ops::BitAnd for Attrs {
type Output = Self;
fn bitand(self, rhs: Self) -> Self::Output {
Self(self.0 & rhs.0)
}
}
impl core::ops::Not for Attrs {
type Output = Self;
fn not(self) -> Self::Output {
Self(!self.0)
}
}
pub mod attrs {
use super::Attrs;
pub const DMA_ATTR_WEAK_ORDERING: Attrs = Attrs(bindings::DMA_ATTR_WEAK_ORDERING);
pub const DMA_ATTR_WRITE_COMBINE: Attrs = Attrs(bindings::DMA_ATTR_WRITE_COMBINE);
pub const DMA_ATTR_NO_KERNEL_MAPPING: Attrs = Attrs(bindings::DMA_ATTR_NO_KERNEL_MAPPING);
pub const DMA_ATTR_SKIP_CPU_SYNC: Attrs = Attrs(bindings::DMA_ATTR_SKIP_CPU_SYNC);
pub const DMA_ATTR_FORCE_CONTIGUOUS: Attrs = Attrs(bindings::DMA_ATTR_FORCE_CONTIGUOUS);
pub const DMA_ATTR_ALLOC_SINGLE_PAGES: Attrs = Attrs(bindings::DMA_ATTR_ALLOC_SINGLE_PAGES);
pub const DMA_ATTR_NO_WARN: Attrs = Attrs(bindings::DMA_ATTR_NO_WARN);
pub const DMA_ATTR_PRIVILEGED: Attrs = Attrs(bindings::DMA_ATTR_PRIVILEGED);
pub const DMA_ATTR_MMIO: Attrs = Attrs(bindings::DMA_ATTR_MMIO);
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum DataDirection {
Bidirectional = Self::const_cast(bindings::dma_data_direction_DMA_BIDIRECTIONAL),
ToDevice = Self::const_cast(bindings::dma_data_direction_DMA_TO_DEVICE),
FromDevice = Self::const_cast(bindings::dma_data_direction_DMA_FROM_DEVICE),
None = Self::const_cast(bindings::dma_data_direction_DMA_NONE),
}
impl DataDirection {
const fn const_cast(val: bindings::dma_data_direction) -> u32 {
let wide_val = val as i128;
if wide_val < 0 || wide_val > u32::MAX as i128 {
build_error!("C enum value is out of bounds for the target type `u32`.");
}
wide_val as u32
}
}
impl From<DataDirection> for bindings::dma_data_direction {
fn from(direction: DataDirection) -> Self {
direction as u32 as Self
}
}
pub struct CoherentAllocation<T: AsBytes + FromBytes> {
dev: ARef<device::Device>,
dma_handle: DmaAddress,
count: usize,
cpu_addr: NonNull<T>,
dma_attrs: Attrs,
}
impl<T: AsBytes + FromBytes> CoherentAllocation<T> {
pub fn alloc_attrs(
dev: &device::Device<Bound>,
count: usize,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<CoherentAllocation<T>> {
build_assert!(
core::mem::size_of::<T>() > 0,
"It doesn't make sense for the allocated type to be a ZST"
);
let size = count
.checked_mul(core::mem::size_of::<T>())
.ok_or(EOVERFLOW)?;
let mut dma_handle = 0;
let addr = unsafe {
bindings::dma_alloc_attrs(
dev.as_raw(),
size,
&mut dma_handle,
gfp_flags.as_raw(),
dma_attrs.as_raw(),
)
};
let addr = NonNull::new(addr).ok_or(ENOMEM)?;
Ok(Self {
dev: dev.into(),
dma_handle,
count,
cpu_addr: addr.cast(),
dma_attrs,
})
}
pub fn alloc_coherent(
dev: &device::Device<Bound>,
count: usize,
gfp_flags: kernel::alloc::Flags,
) -> Result<CoherentAllocation<T>> {
CoherentAllocation::alloc_attrs(dev, count, gfp_flags, Attrs(0))
}
pub fn count(&self) -> usize {
self.count
}
pub fn size(&self) -> usize {
self.count * core::mem::size_of::<T>()
}
pub fn start_ptr(&self) -> *const T {
self.cpu_addr.as_ptr()
}
pub fn start_ptr_mut(&mut self) -> *mut T {
self.cpu_addr.as_ptr()
}
pub fn dma_handle(&self) -> DmaAddress {
self.dma_handle
}
pub fn dma_handle_with_offset(&self, offset: usize) -> Result<DmaAddress> {
if offset >= self.count {
Err(EINVAL)
} else {
Ok(self.dma_handle + (offset * core::mem::size_of::<T>()) as DmaAddress)
}
}
fn validate_range(&self, offset: usize, count: usize) -> Result {
if offset.checked_add(count).ok_or(EOVERFLOW)? > self.count {
return Err(EINVAL);
}
Ok(())
}
pub unsafe fn as_slice(&self, offset: usize, count: usize) -> Result<&[T]> {
self.validate_range(offset, count)?;
Ok(unsafe { core::slice::from_raw_parts(self.start_ptr().add(offset), count) })
}
pub unsafe fn as_slice_mut(&mut self, offset: usize, count: usize) -> Result<&mut [T]> {
self.validate_range(offset, count)?;
Ok(unsafe { core::slice::from_raw_parts_mut(self.start_ptr_mut().add(offset), count) })
}
pub unsafe fn write(&mut self, src: &[T], offset: usize) -> Result {
self.validate_range(offset, src.len())?;
unsafe {
core::ptr::copy_nonoverlapping(
src.as_ptr(),
self.start_ptr_mut().add(offset),
src.len(),
)
};
Ok(())
}
#[doc(hidden)]
pub fn item_from_index(&self, offset: usize) -> Result<*mut T> {
if offset >= self.count {
return Err(EINVAL);
}
Ok(unsafe { self.cpu_addr.as_ptr().add(offset) })
}
#[doc(hidden)]
pub unsafe fn field_read<F: FromBytes>(&self, field: *const F) -> F {
unsafe { field.read_volatile() }
}
#[doc(hidden)]
pub unsafe fn field_write<F: AsBytes>(&self, field: *mut F, val: F) {
unsafe { field.write_volatile(val) }
}
}
impl<T: AsBytes + FromBytes> Drop for CoherentAllocation<T> {
fn drop(&mut self) {
let size = self.count * core::mem::size_of::<T>();
unsafe {
bindings::dma_free_attrs(
self.dev.as_raw(),
size,
self.start_ptr_mut().cast(),
self.dma_handle,
self.dma_attrs.as_raw(),
)
}
}
}
unsafe impl<T: AsBytes + FromBytes + Send> Send for CoherentAllocation<T> {}
#[macro_export]
macro_rules! dma_read {
($dma:expr, $idx: expr, $($field:tt)*) => {{
(|| -> ::core::result::Result<_, $crate::error::Error> {
let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?;
unsafe {
let ptr_field = ::core::ptr::addr_of!((*item) $($field)*);
::core::result::Result::Ok(
$crate::dma::CoherentAllocation::field_read(&$dma, ptr_field)
)
}
})()
}};
($dma:ident [ $idx:expr ] $($field:tt)* ) => {
$crate::dma_read!($dma, $idx, $($field)*)
};
($($dma:ident).* [ $idx:expr ] $($field:tt)* ) => {
$crate::dma_read!($($dma).*, $idx, $($field)*)
};
}
#[macro_export]
macro_rules! dma_write {
($dma:ident [ $idx:expr ] $($field:tt)*) => {{
$crate::dma_write!($dma, $idx, $($field)*)
}};
($($dma:ident).* [ $idx:expr ] $($field:tt)* ) => {{
$crate::dma_write!($($dma).*, $idx, $($field)*)
}};
($dma:expr, $idx: expr, = $val:expr) => {
(|| -> ::core::result::Result<_, $crate::error::Error> {
let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?;
unsafe { $crate::dma::CoherentAllocation::field_write(&$dma, item, $val) }
::core::result::Result::Ok(())
})()
};
($dma:expr, $idx: expr, $(.$field:ident)* = $val:expr) => {
(|| -> ::core::result::Result<_, $crate::error::Error> {
let item = $crate::dma::CoherentAllocation::item_from_index(&$dma, $idx)?;
unsafe {
let ptr_field = ::core::ptr::addr_of_mut!((*item) $(.$field)*);
$crate::dma::CoherentAllocation::field_write(&$dma, ptr_field, $val)
}
::core::result::Result::Ok(())
})()
};
}