use core::{alloc::Layout, marker::PhantomData, ops::Range, ptr::NonNull};
use crate::{
DeviceDma, DmaAddr, DmaCoherency, DmaDirection, DmaDomainId, DmaError, DmaPod,
common::{AllocationKind, DmaAllocation},
};
pub struct CoherentArray<T: DmaPod> {
data: DmaAllocation,
_phantom: PhantomData<T>,
}
unsafe impl<T: DmaPod + Send> Send for CoherentArray<T> {}
unsafe impl<T: DmaPod + Sync> Sync for CoherentArray<T> {}
impl<T: DmaPod> CoherentArray<T> {
pub(crate) fn new_zero_with_align(
os: &DeviceDma,
len: usize,
align: usize,
) -> Result<Self, DmaError> {
let layout = array_layout::<T>(len, align)?;
Ok(Self {
data: DmaAllocation::new_zero_coherent(os, layout)?,
_phantom: PhantomData,
})
}
pub(crate) fn new_zero(os: &DeviceDma, len: usize) -> Result<Self, DmaError> {
Self::new_zero_with_align(os, len, core::mem::align_of::<T>())
}
pub fn dma_addr(&self) -> DmaAddr {
self.data.handle().dma_addr()
}
pub fn len(&self) -> usize {
len_from_bytes::<T>(self.data.handle().size())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn bytes_len(&self) -> usize {
self.data.handle().size()
}
pub fn read_cpu(&self, index: usize) -> Option<T> {
read_at(self.as_ptr(), self.len(), index)
}
pub fn set_cpu(&mut self, index: usize, value: T) {
write_at(self.as_ptr(), self.len(), index, value);
}
pub fn copy_from_slice_cpu(&mut self, src: &[T]) {
copy_from_slice(self.as_ptr(), self.len(), src);
}
pub fn iter_cpu(&self) -> ArrayCpuIter<'_, T, Self> {
ArrayCpuIter {
array: self,
index: 0,
_phantom: PhantomData,
}
}
pub fn write_with_cpu<R>(&mut self, len: usize, f: impl FnOnce(&mut [T]) -> R) -> R {
assert!(len <= self.len(), "range out of bounds");
let data = unsafe { self.as_mut_slice_cpu() };
f(&mut data[..len])
}
pub fn read_with_cpu<R>(&self, len: usize, f: impl FnOnce(&[T]) -> R) -> R {
assert!(len <= self.len(), "range out of bounds");
let data = unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), len) };
f(data)
}
pub fn as_ptr(&self) -> NonNull<T> {
self.data.handle().as_ptr().cast::<T>()
}
pub fn as_slice_cpu(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), self.len()) }
}
pub unsafe fn as_mut_slice_cpu(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.as_ptr().as_ptr(), self.len()) }
}
pub fn try_release(mut self) -> Result<(), DmaError> {
self.data.try_release()
}
}
pub struct ContiguousArray<T: DmaPod> {
data: DmaAllocation,
_phantom: PhantomData<T>,
}
unsafe impl<T: DmaPod + Send> Send for ContiguousArray<T> {}
unsafe impl<T: DmaPod + Sync> Sync for ContiguousArray<T> {}
impl<T: DmaPod> ContiguousArray<T> {
pub(crate) fn new_zero_with_align(
os: &DeviceDma,
len: usize,
align: usize,
direction: DmaDirection,
) -> Result<Self, DmaError> {
let layout = array_layout::<T>(len, align)?;
Ok(Self {
data: DmaAllocation::new_zero_contiguous(os, layout, direction)?,
_phantom: PhantomData,
})
}
pub(crate) fn new_zero(
os: &DeviceDma,
len: usize,
direction: DmaDirection,
) -> Result<Self, DmaError> {
Self::new_zero_with_align(os, len, core::mem::align_of::<T>(), direction)
}
pub fn dma_addr(&self) -> DmaAddr {
self.data.handle().dma_addr()
}
pub fn len(&self) -> usize {
len_from_bytes::<T>(self.data.handle().size())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn bytes_len(&self) -> usize {
self.data.handle().size()
}
pub fn domain_id(&self) -> DmaDomainId {
self.data.device.info().domain()
}
pub fn coherency(&self) -> DmaCoherency {
self.data.device.info().coherency()
}
pub fn direction(&self) -> DmaDirection {
match self.data.kind {
AllocationKind::Contiguous { direction } => direction,
AllocationKind::Coherent => unreachable!("ContiguousArray cannot hold coherent DMA"),
}
}
pub fn read_cpu(&self, index: usize) -> Option<T> {
read_at(self.as_ptr(), self.len(), index)
}
pub fn set_cpu(&mut self, index: usize, value: T) {
write_at(self.as_ptr(), self.len(), index, value);
}
pub fn copy_from_slice_cpu(&mut self, src: &[T]) {
copy_from_slice(self.as_ptr(), self.len(), src);
}
pub fn iter_cpu(&self) -> ArrayCpuIter<'_, T, Self> {
ArrayCpuIter {
array: self,
index: 0,
_phantom: PhantomData,
}
}
pub fn prepare_for_device(&self, range: Range<usize>) {
self.check_range(&range);
self.data.sync_for_device(range.start, range.len());
}
pub fn complete_for_cpu(&self, range: Range<usize>) {
self.check_range(&range);
self.data.sync_for_cpu(range.start, range.len());
}
pub fn write_for_device<R>(&mut self, len: usize, f: impl FnOnce(&mut [T]) -> R) -> R {
let ret = self.write_with_cpu(len, f);
self.prepare_for_device(0..len * core::mem::size_of::<T>());
ret
}
pub fn read_from_device<R>(&self, len: usize, f: impl FnOnce(&[T]) -> R) -> R {
let size = len * core::mem::size_of::<T>();
self.complete_for_cpu(0..size);
self.read_with_cpu(len, f)
}
pub fn copy_to_device_from_slice(&mut self, src: &[T]) {
self.copy_from_slice_cpu(src);
self.prepare_for_device(0..core::mem::size_of_val(src));
}
pub fn copy_from_device_to_slice(&self, dst: &mut [T]) {
self.read_from_device(dst.len(), |src| dst.copy_from_slice(src));
}
pub fn write_with_cpu<R>(&mut self, len: usize, f: impl FnOnce(&mut [T]) -> R) -> R {
assert!(len <= self.len(), "range out of bounds");
{
let data = unsafe { self.as_mut_slice_cpu() };
f(&mut data[..len])
}
}
pub fn read_with_cpu<R>(&self, len: usize, f: impl FnOnce(&[T]) -> R) -> R {
assert!(len <= self.len(), "range out of bounds");
let data = unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), len) };
f(data)
}
pub fn as_ptr(&self) -> NonNull<T> {
self.data.handle().as_ptr().cast::<T>()
}
pub fn as_slice_cpu(&self) -> &[T] {
unsafe { core::slice::from_raw_parts(self.as_ptr().as_ptr(), self.len()) }
}
pub unsafe fn as_mut_slice_cpu(&mut self) -> &mut [T] {
unsafe { core::slice::from_raw_parts_mut(self.as_ptr().as_ptr(), self.len()) }
}
fn check_range(&self, range: &Range<usize>) {
assert!(
range.start <= range.end && range.end <= self.bytes_len(),
"range out of bounds, range: {:?}, bytes_len: {}",
range,
self.bytes_len()
);
}
}
pub trait DmaArrayCpuRead<T: DmaPod> {
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn read_cpu(&self, index: usize) -> Option<T>;
}
impl<T: DmaPod> DmaArrayCpuRead<T> for CoherentArray<T> {
fn len(&self) -> usize {
CoherentArray::len(self)
}
fn is_empty(&self) -> bool {
CoherentArray::is_empty(self)
}
fn read_cpu(&self, index: usize) -> Option<T> {
CoherentArray::read_cpu(self, index)
}
}
impl<T: DmaPod> DmaArrayCpuRead<T> for ContiguousArray<T> {
fn len(&self) -> usize {
ContiguousArray::len(self)
}
fn is_empty(&self) -> bool {
ContiguousArray::is_empty(self)
}
fn read_cpu(&self, index: usize) -> Option<T> {
ContiguousArray::read_cpu(self, index)
}
}
pub struct ArrayCpuIter<'a, T: DmaPod, A: DmaArrayCpuRead<T>> {
array: &'a A,
index: usize,
_phantom: PhantomData<T>,
}
impl<'a, T: DmaPod, A: DmaArrayCpuRead<T>> Iterator for ArrayCpuIter<'a, T, A> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.array.len() {
return None;
}
let value = self.array.read_cpu(self.index);
self.index += 1;
value
}
}
fn array_layout<T>(len: usize, align: usize) -> Result<Layout, DmaError> {
let size = len
.checked_mul(core::mem::size_of::<T>())
.ok_or(DmaError::LayoutError(
Layout::from_size_align(usize::MAX, 1).unwrap_err(),
))?;
Ok(Layout::from_size_align(
size,
align.max(core::mem::align_of::<T>()),
)?)
}
fn len_from_bytes<T>(bytes: usize) -> usize {
if core::mem::size_of::<T>() == 0 {
0
} else {
bytes / core::mem::size_of::<T>()
}
}
fn read_at<T: DmaPod>(ptr: NonNull<T>, len: usize, index: usize) -> Option<T> {
if index >= len {
return None;
}
Some(unsafe { ptr.add(index).read() })
}
fn write_at<T: DmaPod>(ptr: NonNull<T>, len: usize, index: usize, value: T) {
assert!(
index < len,
"index out of range, index: {}, len: {}",
index,
len
);
unsafe { ptr.add(index).write(value) };
}
fn copy_from_slice<T: DmaPod>(ptr: NonNull<T>, len: usize, src: &[T]) {
assert!(
src.len() <= len,
"source slice is larger than DMA array, src len: {}, array len: {}",
src.len(),
len
);
unsafe {
ptr.as_ptr()
.copy_from_nonoverlapping(src.as_ptr(), src.len());
}
}
#[cfg(test)]
mod tests {
use super::*;
fn array_helper_len_and_layout_rules_hold_for_test() -> bool {
assert!(len_from_bytes::<u8>(100) == 100);
assert!(len_from_bytes::<u16>(100) == 50);
assert!(len_from_bytes::<u32>(100) == 25);
assert!(len_from_bytes::<u64>(100) == 12);
let layout = array_layout::<u8>(100, 1);
assert!(layout.is_ok());
let l = layout.unwrap();
assert!(l.size() == 100);
let overflow = array_layout::<u8>(usize::MAX, 1);
assert!(overflow.is_err());
let bad_align = array_layout::<u8>(10, 3); assert!(bad_align.is_err());
assert!(len_from_bytes::<u32>(0) == 0);
let empty = array_layout::<u32>(0, 4);
assert!(empty.is_ok());
assert!(empty.unwrap().size() == 0);
assert!(len_from_bytes::<u16>(5) == 2);
let large_align = array_layout::<u8>(16, 4096); assert!(large_align.is_ok());
assert!(large_align.unwrap().align() == 4096);
true
}
fn array_contiguous_methods_hold_for_test() -> bool {
assert!(array_helper_len_and_layout_rules_hold_for_test());
assert!(len_from_bytes::<u8>(100) == 100);
assert!(len_from_bytes::<u16>(100) == 50);
assert!(len_from_bytes::<u32>(100) == 25);
assert!(len_from_bytes::<u64>(100) == 12);
true
}
fn array_read_at_write_at_helpers_hold_for_test() -> bool {
assert!(len_from_bytes::<u8>(0) == 0);
assert!(len_from_bytes::<u32>(0) == 0);
let empty = array_layout::<u8>(0, 1);
assert!(empty.is_ok());
assert!(empty.unwrap().size() == 0);
true
}
fn array_dma_array_cpu_read_trait_hold_for_test() -> bool {
assert!(len_from_bytes::<u8>(100) == 100);
assert!(len_from_bytes::<u16>(50) == 25);
true
}
fn array_layout_edge_cases_comprehensive_hold_for_test() -> bool {
assert!(len_from_bytes::<()>(100) == 0);
let tiny = array_layout::<u8>(1, 1);
assert!(tiny.is_ok());
assert_eq!(tiny.unwrap().size(), 1);
assert!(array_layout::<u8>(10, 3).is_err());
assert!(array_layout::<u16>(5, 2).is_ok());
assert_eq!(len_from_bytes::<u32>(40), 10);
assert_eq!(len_from_bytes::<u32>(42), 10);
true
}
fn array_copy_from_slice_and_write_at_edge_hold_for_test() -> bool {
assert_eq!(len_from_bytes::<u8>(0), 0);
assert_eq!(len_from_bytes::<u8>(1), 1);
assert_eq!(len_from_bytes::<u8>(255), 255);
assert_eq!(len_from_bytes::<u16>(0), 0);
assert_eq!(len_from_bytes::<u16>(2), 1);
assert_eq!(len_from_bytes::<u16>(3), 1); assert_eq!(len_from_bytes::<u16>(4), 2);
assert_eq!(len_from_bytes::<u64>(0), 0);
assert_eq!(len_from_bytes::<u64>(7), 0); assert_eq!(len_from_bytes::<u64>(8), 1);
assert_eq!(len_from_bytes::<u64>(15), 1); assert_eq!(len_from_bytes::<u64>(16), 2);
assert!(array_layout::<u8>(0, 1).is_ok());
assert!(array_layout::<u8>(1, 1).is_ok());
assert!(array_layout::<u8>(256, 1).is_ok());
assert!(array_layout::<u16>(1, 2).is_ok()); assert!(array_layout::<u16>(100, 2).is_ok());
assert!(array_layout::<u32>(10, 4).is_ok()); assert!(array_layout::<u32>(0, 4).is_ok());
assert!(array_layout::<u64>(5, 8).is_ok());
assert!(array_layout::<u8>(10, 3).is_err()); assert!(array_layout::<u8>(10, 5).is_err()); assert!(array_layout::<u8>(10, 6).is_err()); assert!(array_layout::<u8>(10, 7).is_err()); assert!(array_layout::<u8>(10, 9).is_err());
true
}
fn array_layout_overflow_and_size_align_hold_for_test() -> bool {
let overflow_u8 = array_layout::<u8>(usize::MAX, 1);
assert!(overflow_u8.is_err());
let overflow_u16 = array_layout::<u16>(usize::MAX / 2 + 1, 2);
assert!(overflow_u16.is_err());
let overflow_u32 = array_layout::<u32>(usize::MAX / 4 + 1, 4);
assert!(overflow_u32.is_err());
let large = array_layout::<u8>(1024 * 1024, 4096);
assert!(large.is_ok());
let l = large.unwrap();
assert_eq!(l.size(), 1024 * 1024);
assert_eq!(l.align(), 4096);
assert!(array_layout::<u8>(0, 1).is_ok());
assert!(array_layout::<u8>(0, 2).is_ok());
assert!(array_layout::<u8>(0, 4).is_ok());
assert!(array_layout::<u8>(0, 8).is_ok());
assert!(array_layout::<u8>(0, 16).is_ok());
assert!(array_layout::<u8>(0, 32).is_ok());
assert!(array_layout::<u8>(0, 64).is_ok());
assert!(array_layout::<u8>(0, 128).is_ok());
assert!(array_layout::<u8>(0, 256).is_ok());
assert!(array_layout::<u8>(0, 512).is_ok());
assert!(array_layout::<u8>(0, 1024).is_ok());
assert!(array_layout::<u8>(0, 2048).is_ok());
assert!(array_layout::<u8>(0, 4096).is_ok());
let a1 = array_layout::<u8>(10, 16).unwrap();
assert_eq!(a1.align(), 16);
let a2 = array_layout::<u16>(10, 1).unwrap();
assert_eq!(a2.align(), 2);
true
}
#[test]
fn array_helper_len_and_layout_rules_hold() {
assert!(array_helper_len_and_layout_rules_hold_for_test());
}
#[test]
fn array_contiguous_methods_hold() {
assert!(array_contiguous_methods_hold_for_test());
}
#[test]
fn array_read_at_write_at_helpers_hold() {
assert!(array_read_at_write_at_helpers_hold_for_test());
}
#[test]
fn array_dma_array_cpu_read_trait_hold() {
assert!(array_dma_array_cpu_read_trait_hold_for_test());
}
#[test]
fn array_layout_edge_cases_comprehensive_hold() {
assert!(array_layout_edge_cases_comprehensive_hold_for_test());
}
#[test]
fn array_copy_from_slice_and_write_at_edge_hold() {
assert!(array_copy_from_slice_and_write_at_edge_hold_for_test());
}
#[test]
fn array_layout_overflow_and_size_align_hold() {
assert!(array_layout_overflow_and_size_align_hold_for_test());
}
}