use core::{num::NonZeroUsize, ptr::NonNull};
use mbarrier::mb;
use crate::{DmaAllocHandle, DmaCoherency, DmaConstraints, DmaDirection, DmaError, DmaMapHandle};
cfg_if::cfg_if! {
if #[cfg(target_arch = "aarch64")] {
#[path = "aarch64.rs"]
pub mod arch;
} else{
#[path = "nop.rs"]
pub mod arch;
}
}
pub trait DmaOp: Sync + Send + 'static {
fn page_size(&self) -> usize;
unsafe fn alloc_contiguous(
&self,
constraints: DmaConstraints,
layout: core::alloc::Layout,
) -> Option<DmaAllocHandle>;
unsafe fn dealloc_contiguous(&self, handle: DmaAllocHandle);
unsafe fn alloc_coherent(
&self,
constraints: DmaConstraints,
layout: core::alloc::Layout,
) -> Option<DmaAllocHandle>;
unsafe fn dealloc_coherent(&self, handle: DmaAllocHandle) -> Result<(), DmaError>;
unsafe fn map_streaming(
&self,
constraints: DmaConstraints,
addr: NonNull<u8>,
size: NonZeroUsize,
direction: DmaDirection,
) -> Result<DmaMapHandle, DmaError>;
unsafe fn unmap_streaming(&self, handle: DmaMapHandle);
fn flush(&self, addr: NonNull<u8>, size: usize) {
mb();
arch::flush(addr, size)
}
fn invalidate(&self, addr: NonNull<u8>, size: usize) {
arch::invalidate(addr, size);
mb();
}
fn flush_invalidate(&self, addr: NonNull<u8>, size: usize) {
mb();
arch::flush_invalidate(addr, size);
mb();
}
fn sync_alloc_for_device(
&self,
handle: &DmaAllocHandle,
offset: usize,
size: usize,
direction: DmaDirection,
) {
if matches!(
direction,
DmaDirection::ToDevice | DmaDirection::Bidirectional
) {
self.flush(unsafe { handle.as_ptr().add(offset) }, size);
} else if matches!(direction, DmaDirection::FromDevice) {
self.invalidate(unsafe { handle.as_ptr().add(offset) }, size);
}
}
fn sync_alloc_for_cpu(
&self,
handle: &DmaAllocHandle,
offset: usize,
size: usize,
direction: DmaDirection,
) {
if matches!(
direction,
DmaDirection::FromDevice | DmaDirection::Bidirectional
) {
self.invalidate(unsafe { handle.as_ptr().add(offset) }, size);
}
}
fn sync_map_for_device(
&self,
handle: &DmaMapHandle,
offset: usize,
size: usize,
direction: DmaDirection,
coherency: DmaCoherency,
) {
let source = unsafe { handle.as_ptr().add(offset) };
if let Some(map_virt) = handle.bounce_ptr()
&& map_virt != handle.as_ptr()
{
let target = unsafe { map_virt.add(offset) };
if matches!(
direction,
DmaDirection::ToDevice | DmaDirection::Bidirectional
) {
unsafe {
target
.as_ptr()
.copy_from_nonoverlapping(source.as_ptr(), size);
}
if coherency == DmaCoherency::NonCoherent {
self.flush(target, size);
}
} else if matches!(direction, DmaDirection::FromDevice)
&& coherency == DmaCoherency::NonCoherent
{
self.invalidate(target, size);
}
return;
}
if coherency == DmaCoherency::Coherent {
return;
}
match direction {
DmaDirection::ToDevice => self.flush(source, size),
DmaDirection::FromDevice => self.invalidate(source, size),
DmaDirection::Bidirectional => self.flush_invalidate(source, size),
}
}
fn sync_map_for_cpu(
&self,
handle: &DmaMapHandle,
offset: usize,
size: usize,
direction: DmaDirection,
coherency: DmaCoherency,
) {
if !matches!(
direction,
DmaDirection::FromDevice | DmaDirection::Bidirectional
) {
return;
}
let target = unsafe { handle.as_ptr().add(offset) };
if let Some(map_virt) = handle.bounce_ptr()
&& map_virt != handle.as_ptr()
{
let source = unsafe { map_virt.add(offset) };
if coherency == DmaCoherency::NonCoherent {
self.invalidate(source, size);
}
unsafe {
target
.as_ptr()
.copy_from_nonoverlapping(source.as_ptr(), size);
}
return;
}
if coherency == DmaCoherency::NonCoherent {
self.invalidate(target, size);
}
}
}
#[cfg(test)]
mod tests {
fn dma_op_direction_matching_hold_for_test() -> bool {
use crate::DmaDirection;
let to_device = DmaDirection::ToDevice;
let from_device = DmaDirection::FromDevice;
let bidirectional = DmaDirection::Bidirectional;
assert!(to_device != from_device);
assert!(from_device != bidirectional);
assert!(to_device != bidirectional);
true
}
fn dma_op_constraints_and_error_types_hold_for_test() -> bool {
use crate::DmaError;
let _no_memory = DmaError::NoMemory;
true
}
fn dma_op_sync_direction_branches_hold_for_test() -> bool {
use crate::DmaDirection;
assert!(matches!(DmaDirection::ToDevice, DmaDirection::ToDevice));
assert!(!matches!(DmaDirection::ToDevice, DmaDirection::FromDevice));
assert!(!matches!(
DmaDirection::ToDevice,
DmaDirection::Bidirectional
));
assert!(matches!(DmaDirection::FromDevice, DmaDirection::FromDevice));
assert!(!matches!(DmaDirection::FromDevice, DmaDirection::ToDevice));
assert!(!matches!(
DmaDirection::FromDevice,
DmaDirection::Bidirectional
));
assert!(matches!(
DmaDirection::Bidirectional,
DmaDirection::Bidirectional
));
assert!(!matches!(
DmaDirection::Bidirectional,
DmaDirection::ToDevice
));
assert!(!matches!(
DmaDirection::Bidirectional,
DmaDirection::FromDevice
));
assert!(matches!(
DmaDirection::ToDevice,
DmaDirection::ToDevice | DmaDirection::Bidirectional
));
assert!(!matches!(
DmaDirection::FromDevice,
DmaDirection::ToDevice | DmaDirection::Bidirectional
));
assert!(matches!(
DmaDirection::Bidirectional,
DmaDirection::ToDevice | DmaDirection::Bidirectional
));
true
}
#[test]
fn dma_op_direction_matching_hold() {
assert!(dma_op_direction_matching_hold_for_test());
}
#[test]
fn dma_op_constraints_and_error_types_hold() {
assert!(dma_op_constraints_and_error_types_hold_for_test());
}
#[test]
fn dma_op_sync_direction_branches_hold() {
assert!(dma_op_sync_direction_branches_hold_for_test());
}
}