use core::cell::UnsafeCell;
use core::marker::PhantomData;
use core::mem::size_of;
use crate::hwcore::addr::{DmaAddr, PhysAddr};
pub const CACHE_LINE: usize = 32;
pub enum Read {}
pub enum Write {}
pub trait DcaCache {
fn clean(&mut self, addr: usize, len: usize);
fn invalidate(&mut self, addr: usize, len: usize);
fn clean_invalidate(&mut self, addr: usize, len: usize);
fn barrier(&mut self);
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct NullCache {
pub last: Option<NullCacheOp>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NullCacheOp {
Clean(usize, usize),
Invalidate(usize, usize),
CleanInvalidate(usize, usize),
Barrier,
}
impl DcaCache for NullCache {
fn clean(&mut self, addr: usize, len: usize) {
self.last = Some(NullCacheOp::Clean(addr, len));
}
fn invalidate(&mut self, addr: usize, len: usize) {
self.last = Some(NullCacheOp::Invalidate(addr, len));
}
fn clean_invalidate(&mut self, addr: usize, len: usize) {
self.last = Some(NullCacheOp::CleanInvalidate(addr, len));
}
fn barrier(&mut self) {
self.last = Some(NullCacheOp::Barrier);
}
}
#[cfg(feature = "cortex-m")]
impl DcaCache for cortex_m::peripheral::SCB {
fn clean(&mut self, addr: usize, len: usize) {
unsafe {
cortex_m::peripheral::SCB::clean_dcache_by_address(self, addr, len);
}
}
fn invalidate(&mut self, addr: usize, len: usize) {
unsafe {
cortex_m::peripheral::SCB::invalidate_dcache_by_address(self, addr, len);
}
}
fn clean_invalidate(&mut self, addr: usize, len: usize) {
unsafe {
cortex_m::peripheral::SCB::clean_invalidate_dcache_by_address(self, addr, len);
}
}
fn barrier(&mut self) {
cortex_m::asm::dsb();
}
}
pub struct DcaCacheCtx<'a, C: DcaCache> {
cache: &'a mut C,
}
impl<'a, C: DcaCache> DcaCacheCtx<'a, C> {
#[inline]
pub fn new(cache: &'a mut C) -> Self {
Self { cache }
}
#[inline]
pub fn cache_mut(&mut self) -> &mut C {
self.cache
}
}
#[repr(C, align(32))]
pub struct DcaBuf<T: Copy, const N: usize> {
storage: UnsafeCell<[T; N]>,
}
unsafe impl<T: Copy + Send, const N: usize> Sync for DcaBuf<T, N> {}
impl<T: Copy, const N: usize> DcaBuf<T, N> {
#[inline]
pub const fn new(init: [T; N]) -> Self {
assert!(
(size_of::<T>() * N).is_multiple_of(CACHE_LINE),
"DcaBuf<T, N>: size_of::<T>() * N must be a multiple of CACHE_LINE (32). Pad N upward or wrap T in a cache-line-sized newtype.",
);
assert!(
size_of::<T>() * N > 0,
"DcaBuf<T, N>: empty buffers are not permitted (no DMA target).",
);
Self {
storage: UnsafeCell::new(init),
}
}
#[inline]
pub const fn byte_len(&self) -> usize {
size_of::<T>() * N
}
#[inline]
fn addr_usize(&self) -> usize {
self.storage.get() as *mut u8 as usize
}
pub fn dma_addr(&self) -> DmaAddr {
let addr = self.addr_usize();
let phys = PhysAddr::new(addr as u32);
DmaAddr::from_phys(phys, CACHE_LINE)
.expect("DcaBuf is #[repr(C, align(32))]; storage address is always cache-line aligned")
}
#[inline]
pub fn cpu(&mut self) -> Cpu<'_, T, N> {
Cpu { buf: self }
}
}
pub struct Cpu<'a, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
}
impl<'a, T: Copy, const N: usize> Cpu<'a, T, N> {
#[inline]
pub fn as_slice(&self) -> &[T; N] {
unsafe { &*self.buf.storage.get() }
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T; N] {
unsafe { &mut *self.buf.storage.get() }
}
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.buf.dma_addr()
}
pub fn lend_for_read<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> (DeviceRead<'a, T, N>, DmaAddr) {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.clean(addr, len);
let dma_addr = self.buf.dma_addr();
(DeviceRead { buf: self.buf }, dma_addr)
}
pub fn lend_for_write<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> (DeviceWrite<'a, T, N>, DmaAddr) {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.invalidate(addr, len);
let dma_addr = self.buf.dma_addr();
(DeviceWrite { buf: self.buf }, dma_addr)
}
pub fn start_circular_read<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> CircRead<'a, T, N> {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.clean(addr, len);
CircRead { buf: self.buf }
}
pub fn start_circular_write<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> CircWrite<'a, T, N> {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.invalidate(addr, len);
CircWrite { buf: self.buf }
}
pub fn start_ltdc_scan<C: DcaCache>(self, ctx: &mut DcaCacheCtx<'_, C>) -> LtdcScan<'a, T, N> {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.clean(addr, len);
ctx.cache.barrier();
LtdcScan { buf: self.buf }
}
}
pub struct DeviceRead<'a, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
}
impl<'a, T: Copy, const N: usize> DeviceRead<'a, T, N> {
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.buf.dma_addr()
}
#[inline]
pub fn complete(self) -> Cpu<'a, T, N> {
Cpu { buf: self.buf }
}
}
pub struct DeviceWrite<'a, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
}
impl<'a, T: Copy, const N: usize> DeviceWrite<'a, T, N> {
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.buf.dma_addr()
}
#[inline]
pub fn complete(self) -> Cpu<'a, T, N> {
Cpu { buf: self.buf }
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Half {
First,
Second,
}
pub struct CircRead<'a, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
}
impl<'a, T: Copy, const N: usize> CircRead<'a, T, N> {
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.buf.dma_addr()
}
pub fn half_guard<'b, C: DcaCache>(
&'b mut self,
_ctx: &mut DcaCacheCtx<'_, C>,
half: Half,
) -> HalfGuard<'b, Read, T, N> {
let _ = half_extent::<T, N>(self.buf.addr_usize(), half);
HalfGuard {
buf: self.buf,
half,
_dir: PhantomData,
}
}
pub fn stop_circular<C: DcaCache>(self, ctx: &mut DcaCacheCtx<'_, C>) -> Cpu<'a, T, N> {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.clean(addr, len);
Cpu { buf: self.buf }
}
}
pub struct CircWrite<'a, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
}
impl<'a, T: Copy, const N: usize> CircWrite<'a, T, N> {
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.buf.dma_addr()
}
pub fn half_guard<'b, C: DcaCache>(
&'b mut self,
ctx: &mut DcaCacheCtx<'_, C>,
half: Half,
) -> HalfGuard<'b, Write, T, N> {
let (addr, len) = half_extent::<T, N>(self.buf.addr_usize(), half);
ctx.cache.invalidate(addr, len);
HalfGuard {
buf: self.buf,
half,
_dir: PhantomData,
}
}
pub fn stop_circular<C: DcaCache>(self, ctx: &mut DcaCacheCtx<'_, C>) -> Cpu<'a, T, N> {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.invalidate(addr, len);
Cpu { buf: self.buf }
}
}
pub struct LtdcScan<'a, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
}
impl<'a, T: Copy, const N: usize> LtdcScan<'a, T, N> {
#[inline]
pub fn dma_addr(&self) -> DmaAddr {
self.buf.dma_addr()
}
#[inline]
pub fn paint_full(&mut self) -> &mut [T; N] {
unsafe { &mut *self.buf.storage.get() }
}
pub fn present<C: DcaCache>(&mut self, ctx: &mut DcaCacheCtx<'_, C>) {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.clean(addr, len);
ctx.cache.barrier();
}
pub fn stop_scan<C: DcaCache>(self, ctx: &mut DcaCacheCtx<'_, C>) -> Cpu<'a, T, N> {
let addr = self.buf.addr_usize();
let len = self.buf.byte_len();
ctx.cache.clean(addr, len);
ctx.cache.barrier();
Cpu { buf: self.buf }
}
}
pub struct HalfGuard<'a, DIR, T: Copy, const N: usize> {
buf: &'a mut DcaBuf<T, N>,
half: Half,
_dir: PhantomData<DIR>,
}
impl<'a, DIR, T: Copy, const N: usize> HalfGuard<'a, DIR, T, N> {
#[inline]
pub fn as_slice(&self) -> &[T] {
let (lo, hi) = half_indices::<N>(self.half);
unsafe {
let storage = &*self.buf.storage.get();
core::slice::from_raw_parts(storage.as_ptr().add(lo), hi - lo)
}
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T] {
let (lo, hi) = half_indices::<N>(self.half);
unsafe {
let storage = &mut *self.buf.storage.get();
core::slice::from_raw_parts_mut(storage.as_mut_ptr().add(lo), hi - lo)
}
}
#[inline]
pub fn half(&self) -> Half {
self.half
}
}
impl<'a, T: Copy, const N: usize> HalfGuard<'a, Read, T, N> {
pub fn release<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
current_half: Half,
) -> Result<(), HalfGuardOverrun> {
let (addr, len) = half_extent::<T, N>(self.buf.addr_usize(), self.half);
ctx.cache.clean(addr, len);
check_half_overrun(self.half, current_half)
}
}
impl<'a, T: Copy, const N: usize> HalfGuard<'a, Write, T, N> {
pub fn release<C: DcaCache>(
self,
_ctx: &mut DcaCacheCtx<'_, C>,
current_half: Half,
) -> Result<(), HalfGuardOverrun> {
check_half_overrun(self.half, current_half)
}
}
#[inline]
fn check_half_overrun(guard_half: Half, current_half: Half) -> Result<(), HalfGuardOverrun> {
if current_half == guard_half {
#[cfg(debug_assertions)]
{
panic!(
"DCB HalfGuard overrun: DMA crossed into the inactive half ({guard_half:?}) during the guard's lifetime; INV-D7 violated. Stream is faster than the CPU consumer/producer."
);
}
#[cfg(not(debug_assertions))]
{
return Err(HalfGuardOverrun { half: guard_half });
}
}
Ok(())
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct HalfGuardOverrun {
pub half: Half,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub enum Bank {
M0,
M1,
}
pub struct DcaDoubleBuf<'b, T: Copy, const N: usize> {
m0: &'b mut DcaBuf<T, N>,
m1: &'b mut DcaBuf<T, N>,
}
impl<'b, T: Copy, const N: usize> DcaDoubleBuf<'b, T, N> {
#[inline]
pub fn new(m0: &'b mut DcaBuf<T, N>, m1: &'b mut DcaBuf<T, N>) -> Self {
Self { m0, m1 }
}
#[inline]
pub fn cpu(&mut self) -> DbufCpu<'_, 'b, T, N> {
DbufCpu { buf: self }
}
}
impl<T: Copy, const N: usize> DcaDoubleBuf<'static, T, N> {
pub unsafe fn from_addrs(m0_addr: usize, m1_addr: usize) -> Self {
debug_assert!(
m0_addr.is_multiple_of(CACHE_LINE),
"DcaDoubleBuf::from_addrs: m0_addr must be cache-line aligned (INV-D14)",
);
debug_assert!(
m1_addr.is_multiple_of(CACHE_LINE),
"DcaDoubleBuf::from_addrs: m1_addr must be cache-line aligned (INV-D14)",
);
debug_assert!(
m0_addr != m1_addr,
"DcaDoubleBuf::from_addrs: m0_addr and m1_addr must be distinct",
);
let m0 = unsafe { &mut *(m0_addr as *mut DcaBuf<T, N>) };
let m1 = unsafe { &mut *(m1_addr as *mut DcaBuf<T, N>) };
Self { m0, m1 }
}
}
pub struct DbufCpu<'a, 'b, T: Copy, const N: usize> {
buf: &'a mut DcaDoubleBuf<'b, T, N>,
}
impl<'a, 'b, T: Copy, const N: usize> DbufCpu<'a, 'b, T, N> {
#[inline]
pub fn as_m0_slice(&self) -> &[T; N] {
unsafe { &*self.buf.m0.storage.get() }
}
#[inline]
pub fn as_m1_slice(&self) -> &[T; N] {
unsafe { &*self.buf.m1.storage.get() }
}
#[inline]
pub fn as_m0_mut_slice(&mut self) -> &mut [T; N] {
unsafe { &mut *self.buf.m0.storage.get() }
}
#[inline]
pub fn as_m1_mut_slice(&mut self) -> &mut [T; N] {
unsafe { &mut *self.buf.m1.storage.get() }
}
#[inline]
pub fn m0_dma_addr(&self) -> DmaAddr {
self.buf.m0.dma_addr()
}
#[inline]
pub fn m1_dma_addr(&self) -> DmaAddr {
self.buf.m1.dma_addr()
}
pub fn start_double_buffer_read<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> DbufRead<'a, 'b, T, N> {
let m0_addr = self.buf.m0.addr_usize();
let m1_addr = self.buf.m1.addr_usize();
let len = self.buf.m0.byte_len();
ctx.cache.clean(m0_addr, len);
ctx.cache.clean(m1_addr, len);
DbufRead { buf: self.buf }
}
pub fn start_double_buffer_write<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> DbufWrite<'a, 'b, T, N> {
let m0_addr = self.buf.m0.addr_usize();
let m1_addr = self.buf.m1.addr_usize();
let len = self.buf.m0.byte_len();
ctx.cache.invalidate(m0_addr, len);
ctx.cache.invalidate(m1_addr, len);
DbufWrite { buf: self.buf }
}
}
pub struct DbufRead<'a, 'b, T: Copy, const N: usize> {
buf: &'a mut DcaDoubleBuf<'b, T, N>,
}
impl<'a, 'b, T: Copy, const N: usize> DbufRead<'a, 'b, T, N> {
#[inline]
pub fn m0_dma_addr(&self) -> DmaAddr {
self.buf.m0.dma_addr()
}
#[inline]
pub fn m1_dma_addr(&self) -> DmaAddr {
self.buf.m1.dma_addr()
}
pub fn bank_guard<'g, C: DcaCache>(
&'g mut self,
_ctx: &mut DcaCacheCtx<'_, C>,
current_target: Bank,
) -> BankGuard<'g, Read, T, N> {
let exposed_bank = inactive_bank(current_target);
let exposed = match exposed_bank {
Bank::M0 => &mut *self.buf.m0,
Bank::M1 => &mut *self.buf.m1,
};
BankGuard {
bank_storage: exposed,
bank: exposed_bank,
_dir: PhantomData,
}
}
pub fn stop_double_buffer<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> DbufCpu<'a, 'b, T, N> {
let m0_addr = self.buf.m0.addr_usize();
let m1_addr = self.buf.m1.addr_usize();
let len = self.buf.m0.byte_len();
ctx.cache.clean(m0_addr, len);
ctx.cache.clean(m1_addr, len);
DbufCpu { buf: self.buf }
}
}
pub struct DbufWrite<'a, 'b, T: Copy, const N: usize> {
buf: &'a mut DcaDoubleBuf<'b, T, N>,
}
impl<'a, 'b, T: Copy, const N: usize> DbufWrite<'a, 'b, T, N> {
#[inline]
pub fn m0_dma_addr(&self) -> DmaAddr {
self.buf.m0.dma_addr()
}
#[inline]
pub fn m1_dma_addr(&self) -> DmaAddr {
self.buf.m1.dma_addr()
}
pub fn bank_guard<'g, C: DcaCache>(
&'g mut self,
ctx: &mut DcaCacheCtx<'_, C>,
current_target: Bank,
) -> BankGuard<'g, Write, T, N> {
let exposed_bank = inactive_bank(current_target);
let exposed = match exposed_bank {
Bank::M0 => &mut *self.buf.m0,
Bank::M1 => &mut *self.buf.m1,
};
let addr = exposed.addr_usize();
let len = exposed.byte_len();
ctx.cache.invalidate(addr, len);
BankGuard {
bank_storage: exposed,
bank: exposed_bank,
_dir: PhantomData,
}
}
pub fn stop_double_buffer<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
) -> DbufCpu<'a, 'b, T, N> {
let m0_addr = self.buf.m0.addr_usize();
let m1_addr = self.buf.m1.addr_usize();
let len = self.buf.m0.byte_len();
ctx.cache.invalidate(m0_addr, len);
ctx.cache.invalidate(m1_addr, len);
DbufCpu { buf: self.buf }
}
}
pub struct BankGuard<'a, DIR, T: Copy, const N: usize> {
bank_storage: &'a mut DcaBuf<T, N>,
bank: Bank,
_dir: PhantomData<DIR>,
}
impl<'a, DIR, T: Copy, const N: usize> BankGuard<'a, DIR, T, N> {
#[inline]
pub fn as_slice(&self) -> &[T; N] {
unsafe { &*self.bank_storage.storage.get() }
}
#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T; N] {
unsafe { &mut *self.bank_storage.storage.get() }
}
#[inline]
pub fn bank(&self) -> Bank {
self.bank
}
}
impl<'a, T: Copy, const N: usize> BankGuard<'a, Read, T, N> {
pub fn release<C: DcaCache>(
self,
ctx: &mut DcaCacheCtx<'_, C>,
current_target: Bank,
) -> Result<(), BankGuardOverrun> {
let addr = self.bank_storage.addr_usize();
let len = self.bank_storage.byte_len();
ctx.cache.clean(addr, len);
check_bank_overrun(self.bank, current_target)
}
}
impl<'a, T: Copy, const N: usize> BankGuard<'a, Write, T, N> {
pub fn release<C: DcaCache>(
self,
_ctx: &mut DcaCacheCtx<'_, C>,
current_target: Bank,
) -> Result<(), BankGuardOverrun> {
check_bank_overrun(self.bank, current_target)
}
}
#[inline]
fn check_bank_overrun(guard_bank: Bank, current_target: Bank) -> Result<(), BankGuardOverrun> {
if current_target == guard_bank {
#[cfg(debug_assertions)]
{
panic!(
"DCB BankGuard overrun: DMA flipped CT into the inactive bank ({guard_bank:?}) during the guard's lifetime; INV-D15 violated. Stream is faster than the CPU consumer/producer."
);
}
#[cfg(not(debug_assertions))]
{
return Err(BankGuardOverrun { bank: guard_bank });
}
}
Ok(())
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct BankGuardOverrun {
pub bank: Bank,
}
#[inline]
const fn inactive_bank(current_target: Bank) -> Bank {
match current_target {
Bank::M0 => Bank::M1,
Bank::M1 => Bank::M0,
}
}
fn half_extent<T: Copy, const N: usize>(base_addr: usize, half: Half) -> (usize, usize) {
let total = size_of::<T>() * N;
debug_assert!(
total.is_multiple_of(2 * CACHE_LINE),
"DcaBuf used with half_guard must have N*sizeof(T) divisible by 2*CACHE_LINE so each half is itself cache-line aligned",
);
let half_bytes = total / 2;
let off = match half {
Half::First => 0,
Half::Second => half_bytes,
};
(base_addr + off, half_bytes)
}
const fn half_indices<const N: usize>(half: Half) -> (usize, usize) {
let mid = N / 2;
match half {
Half::First => (0, mid),
Half::Second => (mid, N),
}
}
#[cfg(test)]
mod tests {
use super::*;
type Buf = DcaBuf<i16, 16>;
#[test]
fn dcabuf_alignment_is_32() {
let buf = Buf::new([0; 16]);
let addr = (&buf as *const Buf) as usize;
assert_eq!(addr % CACHE_LINE, 0, "INV-D1: 32-byte alignment");
}
#[test]
fn dcabuf_size_is_multiple_of_cache_line() {
assert_eq!(core::mem::size_of::<Buf>() % CACHE_LINE, 0);
}
#[test]
fn cpu_round_trip_through_device_read() {
let mut buf = Buf::new([0; 16]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let (pending, _addr) = cpu.lend_for_read(&mut ctx);
let _ = pending.complete();
}
#[test]
fn lend_for_read_emits_clean() {
let mut buf = Buf::new([0; 16]);
let mut cache = NullCache::default();
{
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let (pending, _addr) = cpu.lend_for_read(&mut ctx);
let _ = pending.complete();
}
assert!(matches!(cache.last, Some(NullCacheOp::Clean(_, 32))));
}
#[test]
fn lend_for_write_emits_invalidate() {
let mut buf = Buf::new([0; 16]);
let mut cache = NullCache::default();
{
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let (pending, _addr) = cpu.lend_for_write(&mut ctx);
let _ = pending.complete();
}
assert!(matches!(cache.last, Some(NullCacheOp::Invalidate(_, 32))));
}
#[test]
fn cpu_can_read_and_write_storage() {
let mut buf = Buf::new([0; 16]);
let mut cpu = buf.cpu();
cpu.as_mut_slice()[3] = 42;
assert_eq!(cpu.as_slice()[3], 42);
}
#[test]
fn cpu_dma_addr_is_cache_line_aligned() {
let mut buf = Buf::new([0; 16]);
let cpu = buf.cpu();
let addr = cpu.dma_addr();
assert_eq!(addr.raw() as usize % CACHE_LINE, 0);
}
#[test]
fn circ_read_half_guard_emits_clean_per_half() {
type CircBuf = DcaBuf<u8, 64>;
let mut buf = CircBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let mut circ = cpu.start_circular_read(&mut ctx);
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Clean(_, 64))
));
let mut guard = circ.half_guard(&mut ctx, Half::Second);
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Clean(_, 64))
));
guard.as_mut_slice()[0] = 0xAB;
guard.release(&mut ctx, Half::First).unwrap();
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Clean(_, 32))
));
let _cpu = circ.stop_circular(&mut ctx);
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Clean(_, 64))
));
}
#[test]
fn circ_write_half_guard_emits_invalidate_per_half() {
type CircBuf = DcaBuf<u8, 64>;
let mut buf = CircBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let mut circ = cpu.start_circular_write(&mut ctx);
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Invalidate(_, 64))
));
{
let _guard = circ.half_guard(&mut ctx, Half::First);
}
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Invalidate(_, 32))
));
let _cpu = circ.stop_circular(&mut ctx);
assert!(matches!(
ctx.cache_mut().last,
Some(NullCacheOp::Invalidate(_, 64))
));
}
#[test]
fn half_indices_split_evenly() {
assert_eq!(half_indices::<16>(Half::First), (0, 8));
assert_eq!(half_indices::<16>(Half::Second), (8, 16));
}
#[test]
#[cfg(not(debug_assertions))]
fn half_guard_release_returns_err_on_overrun_in_release() {
type CircBuf = DcaBuf<u8, 64>;
let mut buf = CircBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let mut circ = cpu.start_circular_read(&mut ctx);
let guard = circ.half_guard(&mut ctx, Half::Second);
assert_eq!(
guard.release(&mut ctx, Half::Second),
Err(HalfGuardOverrun { half: Half::Second })
);
}
type DbBank = DcaBuf<i16, 16>;
#[test]
fn dbufcpu_round_trip_through_active_double_buffer_write() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = dca.cpu();
let active = cpu.start_double_buffer_write(&mut ctx);
let _cpu_back = active.stop_double_buffer(&mut ctx);
}
#[test]
fn dbufcpu_can_read_and_write_either_bank() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cpu = dca.cpu();
cpu.as_m0_mut_slice()[3] = 42;
cpu.as_m1_mut_slice()[7] = 99;
assert_eq!(cpu.as_m0_slice()[3], 42);
assert_eq!(cpu.as_m1_slice()[7], 99);
}
#[test]
fn dbufcpu_dma_addrs_are_distinct_and_aligned() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let cpu = dca.cpu();
let a0 = cpu.m0_dma_addr().raw() as usize;
let a1 = cpu.m1_dma_addr().raw() as usize;
assert_eq!(a0 % CACHE_LINE, 0);
assert_eq!(a1 % CACHE_LINE, 0);
assert_ne!(a0, a1, "M0 / M1 must address distinct memory");
}
#[test]
fn start_double_buffer_read_cleans_both_banks() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cache = NullCache::default();
{
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = dca.cpu();
let active = cpu.start_double_buffer_read(&mut ctx);
let _ = active.stop_double_buffer(&mut ctx);
}
assert!(matches!(cache.last, Some(NullCacheOp::Clean(_, 32))));
}
#[test]
fn start_double_buffer_write_invalidates_both_banks() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cache = NullCache::default();
{
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = dca.cpu();
let active = cpu.start_double_buffer_write(&mut ctx);
let _ = active.stop_double_buffer(&mut ctx);
}
assert!(matches!(cache.last, Some(NullCacheOp::Invalidate(_, 32))));
}
#[test]
fn dbuf_read_bank_guard_emits_clean_on_inactive_bank() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let m0_addr = (&raw const m0) as usize;
let m1_addr = (&raw const m1) as usize;
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = dca.cpu();
let mut active = cpu.start_double_buffer_read(&mut ctx);
let mut guard = active.bank_guard(&mut ctx, Bank::M0);
assert_eq!(guard.bank(), Bank::M1);
guard.as_mut_slice()[0] = 0xAB;
guard.release(&mut ctx, Bank::M0).unwrap();
let last = ctx.cache_mut().last.unwrap();
match last {
NullCacheOp::Clean(addr, len) => {
assert_eq!(len, 32);
assert_eq!(addr, m1_addr, "expected M1 (inactive) cleaned");
assert_ne!(addr, m0_addr);
}
other => panic!("expected Clean, got {other:?}"),
}
}
#[test]
fn dbuf_write_bank_guard_emits_invalidate_on_inactive_bank() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let m0_addr = (&raw const m0) as usize;
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = dca.cpu();
let mut active = cpu.start_double_buffer_write(&mut ctx);
let guard = active.bank_guard(&mut ctx, Bank::M1);
assert_eq!(guard.bank(), Bank::M0);
let last = ctx.cache_mut().last.unwrap();
match last {
NullCacheOp::Invalidate(addr, len) => {
assert_eq!(len, 32);
assert_eq!(addr, m0_addr, "expected M0 (inactive) invalidated");
}
other => panic!("expected Invalidate, got {other:?}"),
}
guard.release(&mut ctx, Bank::M1).unwrap();
}
#[test]
fn inactive_bank_swaps_correctly() {
assert_eq!(inactive_bank(Bank::M0), Bank::M1);
assert_eq!(inactive_bank(Bank::M1), Bank::M0);
}
#[test]
#[cfg(not(debug_assertions))]
fn bank_guard_release_returns_err_on_overrun_in_release() {
let mut m0 = DbBank::new([0; 16]);
let mut m1 = DbBank::new([0; 16]);
let mut dca = DcaDoubleBuf::new(&mut m0, &mut m1);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = dca.cpu();
let mut active = cpu.start_double_buffer_read(&mut ctx);
let guard = active.bank_guard(&mut ctx, Bank::M0);
assert_eq!(
guard.release(&mut ctx, Bank::M1),
Err(BankGuardOverrun { bank: Bank::M1 })
);
}
type ScanBuf = DcaBuf<u8, 64>;
#[test]
fn ltdc_scan_round_trip_through_cpu() {
let mut buf = ScanBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let scan = cpu.start_ltdc_scan(&mut ctx);
let _cpu_back = scan.stop_scan(&mut ctx);
}
#[test]
fn start_ltdc_scan_emits_clean_then_barrier() {
let mut buf = ScanBuf::new([0; 64]);
let mut cache = NullCache::default();
{
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let scan = cpu.start_ltdc_scan(&mut ctx);
assert_eq!(ctx.cache_mut().last, Some(NullCacheOp::Barrier));
let _ = scan.stop_scan(&mut ctx);
}
assert_eq!(cache.last, Some(NullCacheOp::Barrier));
}
#[test]
fn ltdc_scan_paint_full_yields_buffer_slice() {
let mut buf = ScanBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let mut scan = cpu.start_ltdc_scan(&mut ctx);
let pixels = scan.paint_full();
pixels[0] = 0xAA;
pixels[63] = 0x55;
assert_eq!(scan.paint_full()[0], 0xAA);
assert_eq!(scan.paint_full()[63], 0x55);
}
#[test]
fn ltdc_scan_present_emits_clean_then_barrier_no_transition() {
let mut buf = ScanBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let mut scan = cpu.start_ltdc_scan(&mut ctx);
scan.paint_full()[5] = 0x42;
scan.present(&mut ctx);
assert_eq!(ctx.cache_mut().last, Some(NullCacheOp::Barrier));
scan.paint_full()[7] = 0x33;
scan.present(&mut ctx);
let _ = scan.stop_scan(&mut ctx);
}
#[test]
fn ltdc_scan_dma_addr_is_cache_line_aligned() {
let mut buf = ScanBuf::new([0; 64]);
let mut cache = NullCache::default();
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let scan = cpu.start_ltdc_scan(&mut ctx);
let addr = scan.dma_addr();
assert_eq!(addr.raw() as usize % CACHE_LINE, 0);
let _ = scan.stop_scan(&mut ctx);
}
#[test]
fn ltdc_scan_present_clean_extent_is_full_buffer() {
struct LoggingCache {
ops: alloc::vec::Vec<NullCacheOp>,
}
impl DcaCache for LoggingCache {
fn clean(&mut self, addr: usize, len: usize) {
self.ops.push(NullCacheOp::Clean(addr, len));
}
fn invalidate(&mut self, addr: usize, len: usize) {
self.ops.push(NullCacheOp::Invalidate(addr, len));
}
fn clean_invalidate(&mut self, addr: usize, len: usize) {
self.ops.push(NullCacheOp::CleanInvalidate(addr, len));
}
fn barrier(&mut self) {
self.ops.push(NullCacheOp::Barrier);
}
}
let mut buf = ScanBuf::new([0; 64]);
let mut cache = LoggingCache {
ops: alloc::vec::Vec::new(),
};
{
let mut ctx = DcaCacheCtx::new(&mut cache);
let cpu = buf.cpu();
let mut scan = cpu.start_ltdc_scan(&mut ctx);
scan.present(&mut ctx);
let _ = scan.stop_scan(&mut ctx);
}
assert_eq!(cache.ops.len(), 6);
for (i, op) in cache.ops.iter().enumerate() {
if i % 2 == 0 {
assert!(
matches!(op, NullCacheOp::Clean(_, 64)),
"expected Clean(_, 64) at index {i}, got {op:?}"
);
} else {
assert_eq!(*op, NullCacheOp::Barrier);
}
}
}
}