use crate::{
bindings,
debugfs,
device::{
self,
Bound,
Core, },
error::to_result,
fs::file,
prelude::*,
ptr::KnownSize,
sync::aref::ARef,
transmute::{
AsBytes,
FromBytes, }, uaccess::UserSliceWriter,
};
use core::{
ops::{
Deref,
DerefMut, },
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())
})
}
unsafe fn dma_set_max_seg_size(&self, size: u32) {
unsafe { bindings::dma_set_max_seg_size(self.as_ref().as_raw(), size) }
}
}
#[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_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 CoherentBox<T: KnownSize + ?Sized>(Coherent<T>);
impl<T: AsBytes + FromBytes> CoherentBox<[T]> {
#[inline]
pub fn zeroed_slice_with_attrs(
dev: &device::Device<Bound>,
count: usize,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Self> {
Coherent::zeroed_slice_with_attrs(dev, count, gfp_flags, dma_attrs).map(Self)
}
#[inline]
pub fn zeroed_slice(
dev: &device::Device<Bound>,
count: usize,
gfp_flags: kernel::alloc::Flags,
) -> Result<Self> {
Self::zeroed_slice_with_attrs(dev, count, gfp_flags, Attrs(0))
}
pub fn init_at<E>(&mut self, i: usize, init: impl Init<T, E>) -> Result
where
Error: From<E>,
{
if i >= self.0.len() {
return Err(EINVAL);
}
let ptr = &raw mut self[i];
unsafe { init.__init(ptr)? };
Ok(())
}
pub fn from_slice_with_attrs(
dev: &device::Device<Bound>,
data: &[T],
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Self>
where
T: Copy,
{
let mut slice = Self(Coherent::<T>::alloc_slice_with_attrs(
dev,
data.len(),
gfp_flags,
dma_attrs,
)?);
slice.copy_from_slice(data);
Ok(slice)
}
#[inline]
pub fn from_slice(
dev: &device::Device<Bound>,
data: &[T],
gfp_flags: kernel::alloc::Flags,
) -> Result<Self>
where
T: Copy,
{
Self::from_slice_with_attrs(dev, data, gfp_flags, Attrs(0))
}
}
impl<T: AsBytes + FromBytes> CoherentBox<T> {
#[inline]
pub fn zeroed_with_attrs(
dev: &device::Device<Bound>,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Self> {
Coherent::zeroed_with_attrs(dev, gfp_flags, dma_attrs).map(Self)
}
#[inline]
pub fn zeroed(dev: &device::Device<Bound>, gfp_flags: kernel::alloc::Flags) -> Result<Self> {
Self::zeroed_with_attrs(dev, gfp_flags, Attrs(0))
}
}
impl<T: KnownSize + ?Sized> Deref for CoherentBox<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { self.0.as_ref() }
}
}
impl<T: AsBytes + FromBytes + KnownSize + ?Sized> DerefMut for CoherentBox<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { self.0.as_mut() }
}
}
impl<T: AsBytes + FromBytes + KnownSize + ?Sized> From<CoherentBox<T>> for Coherent<T> {
#[inline]
fn from(value: CoherentBox<T>) -> Self {
value.0
}
}
pub struct Coherent<T: KnownSize + ?Sized> {
dev: ARef<device::Device>,
dma_handle: DmaAddress,
cpu_addr: NonNull<T>,
dma_attrs: Attrs,
}
impl<T: KnownSize + ?Sized> Coherent<T> {
#[inline]
pub fn size(&self) -> usize {
T::size(self.cpu_addr.as_ptr())
}
#[inline]
pub fn as_ptr(&self) -> *const T {
self.cpu_addr.as_ptr()
}
#[inline]
pub fn as_mut_ptr(&self) -> *mut T {
self.cpu_addr.as_ptr()
}
#[inline]
pub fn dma_handle(&self) -> DmaAddress {
self.dma_handle
}
#[inline]
pub unsafe fn as_ref(&self) -> &T {
unsafe { &*self.as_ptr() }
}
#[expect(clippy::mut_from_ref, reason = "unsafe to use API")]
#[inline]
pub unsafe fn as_mut(&self) -> &mut T {
unsafe { &mut *self.as_mut_ptr() }
}
#[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> Coherent<T> {
fn alloc_with_attrs(
dev: &device::Device<Bound>,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Self> {
const {
assert!(
core::mem::size_of::<T>() > 0,
"It doesn't make sense for the allocated type to be a ZST"
);
}
let mut dma_handle = 0;
let addr = unsafe {
bindings::dma_alloc_attrs(
dev.as_raw(),
core::mem::size_of::<T>(),
&mut dma_handle,
gfp_flags.as_raw(),
dma_attrs.as_raw(),
)
};
let cpu_addr = NonNull::new(addr.cast()).ok_or(ENOMEM)?;
Ok(Self {
dev: dev.into(),
dma_handle,
cpu_addr,
dma_attrs,
})
}
#[inline]
pub fn zeroed_with_attrs(
dev: &device::Device<Bound>,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Self> {
Self::alloc_with_attrs(dev, gfp_flags | __GFP_ZERO, dma_attrs)
}
#[inline]
pub fn zeroed(dev: &device::Device<Bound>, gfp_flags: kernel::alloc::Flags) -> Result<Self> {
Self::zeroed_with_attrs(dev, gfp_flags, Attrs(0))
}
pub fn init_with_attrs<E>(
dev: &device::Device<Bound>,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
init: impl Init<T, E>,
) -> Result<Self>
where
Error: From<E>,
{
let dmem = Self::alloc_with_attrs(dev, gfp_flags, dma_attrs)?;
let ptr = dmem.as_mut_ptr();
unsafe { init.__init(ptr)? };
Ok(dmem)
}
#[inline]
pub fn init<E>(
dev: &device::Device<Bound>,
gfp_flags: kernel::alloc::Flags,
init: impl Init<T, E>,
) -> Result<Self>
where
Error: From<E>,
{
Self::init_with_attrs(dev, gfp_flags, Attrs(0), init)
}
fn alloc_slice_with_attrs(
dev: &device::Device<Bound>,
len: usize,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Coherent<[T]>> {
const {
assert!(
core::mem::size_of::<T>() > 0,
"It doesn't make sense for the allocated type to be a ZST"
);
}
if len == 0 {
Err(EINVAL)?;
}
let size = core::mem::size_of::<T>().checked_mul(len).ok_or(ENOMEM)?;
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 cpu_addr = NonNull::slice_from_raw_parts(NonNull::new(addr.cast()).ok_or(ENOMEM)?, len);
Ok(Coherent {
dev: dev.into(),
dma_handle,
cpu_addr,
dma_attrs,
})
}
#[inline]
pub fn zeroed_slice_with_attrs(
dev: &device::Device<Bound>,
len: usize,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Coherent<[T]>> {
Coherent::alloc_slice_with_attrs(dev, len, gfp_flags | __GFP_ZERO, dma_attrs)
}
#[inline]
pub fn zeroed_slice(
dev: &device::Device<Bound>,
len: usize,
gfp_flags: kernel::alloc::Flags,
) -> Result<Coherent<[T]>> {
Self::zeroed_slice_with_attrs(dev, len, gfp_flags, Attrs(0))
}
#[inline]
pub fn from_slice_with_attrs(
dev: &device::Device<Bound>,
data: &[T],
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Coherent<[T]>>
where
T: Copy,
{
CoherentBox::from_slice_with_attrs(dev, data, gfp_flags, dma_attrs).map(Into::into)
}
#[inline]
pub fn from_slice(
dev: &device::Device<Bound>,
data: &[T],
gfp_flags: kernel::alloc::Flags,
) -> Result<Coherent<[T]>>
where
T: Copy,
{
Self::from_slice_with_attrs(dev, data, gfp_flags, Attrs(0))
}
}
impl<T> Coherent<[T]> {
#[inline]
#[expect(clippy::len_without_is_empty, reason = "Coherent slice is never empty")]
pub fn len(&self) -> usize {
self.cpu_addr.len()
}
}
impl<T: KnownSize + ?Sized> Drop for Coherent<T> {
fn drop(&mut self) {
let size = T::size(self.cpu_addr.as_ptr());
unsafe {
bindings::dma_free_attrs(
self.dev.as_raw(),
size,
self.cpu_addr.as_ptr().cast(),
self.dma_handle,
self.dma_attrs.as_raw(),
)
}
}
}
unsafe impl<T: KnownSize + Send + ?Sized> Send for Coherent<T> {}
unsafe impl<T: KnownSize + ?Sized + AsBytes + FromBytes + Sync> Sync for Coherent<T> {}
impl<T: KnownSize + AsBytes + ?Sized> debugfs::BinaryWriter for Coherent<T> {
fn write_to_slice(
&self,
writer: &mut UserSliceWriter,
offset: &mut file::Offset,
) -> Result<usize> {
if offset.is_negative() {
return Err(EINVAL);
}
let Ok(offset_val) = usize::try_from(*offset) else {
return Ok(0);
};
let count = self.size().saturating_sub(offset_val).min(writer.len());
writer.write_dma(self, offset_val, count)?;
*offset += count as i64;
Ok(count)
}
}
pub struct CoherentHandle {
dev: ARef<device::Device>,
dma_handle: DmaAddress,
cpu_handle: NonNull<c_void>,
size: usize,
dma_attrs: Attrs,
}
impl CoherentHandle {
pub fn alloc_with_attrs(
dev: &device::Device<Bound>,
size: usize,
gfp_flags: kernel::alloc::Flags,
dma_attrs: Attrs,
) -> Result<Self> {
if size == 0 {
return Err(EINVAL);
}
let dma_attrs = dma_attrs | Attrs(bindings::DMA_ATTR_NO_KERNEL_MAPPING);
let mut dma_handle = 0;
let cpu_handle = unsafe {
bindings::dma_alloc_attrs(
dev.as_raw(),
size,
&mut dma_handle,
gfp_flags.as_raw(),
dma_attrs.as_raw(),
)
};
let cpu_handle = NonNull::new(cpu_handle).ok_or(ENOMEM)?;
Ok(Self {
dev: dev.into(),
dma_handle,
cpu_handle,
size,
dma_attrs,
})
}
#[inline]
pub fn alloc(
dev: &device::Device<Bound>,
size: usize,
gfp_flags: kernel::alloc::Flags,
) -> Result<Self> {
Self::alloc_with_attrs(dev, size, gfp_flags, Attrs(0))
}
#[inline]
pub fn dma_handle(&self) -> DmaAddress {
self.dma_handle
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
}
impl Drop for CoherentHandle {
fn drop(&mut self) {
unsafe {
bindings::dma_free_attrs(
self.dev.as_raw(),
self.size,
self.cpu_handle.as_ptr(),
self.dma_handle,
self.dma_attrs.as_raw(),
)
}
}
}
unsafe impl Send for CoherentHandle {}
unsafe impl Sync for CoherentHandle {}
#[macro_export]
macro_rules! dma_read {
($dma:expr, $($proj:tt)*) => {{
let dma = &$dma;
let ptr = $crate::ptr::project!(
$crate::dma::Coherent::as_ptr(dma), $($proj)*
);
unsafe { $crate::dma::Coherent::field_read(dma, ptr) }
}};
}
#[macro_export]
macro_rules! dma_write {
(@parse [$dma:expr] [$($proj:tt)*] [, $val:expr]) => {{
let dma = &$dma;
let ptr = $crate::ptr::project!(
mut $crate::dma::Coherent::as_mut_ptr(dma), $($proj)*
);
let val = $val;
unsafe { $crate::dma::Coherent::field_write(dma, ptr, val) }
}};
(@parse [$dma:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => {
$crate::dma_write!(@parse [$dma] [$($proj)* .$field] [$($rest)*])
};
(@parse [$dma:expr] [$($proj:tt)*] [[$index:expr]? $($rest:tt)*]) => {
$crate::dma_write!(@parse [$dma] [$($proj)* [$index]?] [$($rest)*])
};
(@parse [$dma:expr] [$($proj:tt)*] [[$index:expr] $($rest:tt)*]) => {
$crate::dma_write!(@parse [$dma] [$($proj)* [$index]] [$($rest)*])
};
($dma:expr, $($rest:tt)*) => {
$crate::dma_write!(@parse [$dma] [] [$($rest)*])
};
}