1#[cfg(target_arch = "riscv32")]
8use core::sync::atomic::{compiler_fence, fence, Ordering};
9
10use crate::{descriptors::BUF_SIZE, regs, RDes, TDes};
11
12const CACHE_LINE_SIZE: usize = 128;
17const CACHE_LINE_MASK: usize = CACHE_LINE_SIZE - 1;
18
19#[cfg(target_arch = "riscv32")]
28const CACHE_MAP_L1_DCACHE: u32 = 1 << 4;
29#[cfg(target_arch = "riscv32")]
30const CACHE_MAP_L2_CACHE: u32 = 1 << 5;
31
32#[cfg(target_arch = "riscv32")]
40type RomCacheOp = unsafe extern "C" fn(map: u32, addr: u32, size: u32) -> i32;
41#[cfg(target_arch = "riscv32")]
42const ROM_CACHE_INVALIDATE_ADDR: usize = 0x4FC0_03E4;
43#[cfg(target_arch = "riscv32")]
44const ROM_CACHE_INVALIDATE_ALL: usize = 0x4FC0_0404;
45#[cfg(target_arch = "riscv32")]
46const ROM_CACHE_WRITEBACK_ALL: usize = 0x4FC0_0414;
47#[cfg(target_arch = "riscv32")]
48type RomCacheAll = unsafe extern "C" fn(map: u32) -> i32;
49
50#[cfg(target_arch = "riscv32")]
58const ROM_CACHE_SET_L2_CACHE_MODE: usize = 0x4FC0_03D4;
59#[cfg(target_arch = "riscv32")]
60type RomCacheSetL2Mode = unsafe extern "C" fn(size: u32, ways: u32, line_size: u32);
61
62#[cfg(target_arch = "riscv32")]
65const CACHE_SIZE_256K: u32 = 10;
66#[cfg(target_arch = "riscv32")]
67const CACHE_8WAYS_ASSOC: u32 = 2;
68#[cfg(target_arch = "riscv32")]
69const CACHE_LINE_SIZE_64B: u32 = 3;
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72struct CacheAlignedRange {
73 start: usize,
74 size: usize,
75}
76
77const DMA_RESET_TIMEOUT_POLLS: usize = 1_000;
78const DMA_BURST_LENGTH: u32 = 8;
79
80#[derive(Clone, Copy, Debug, Eq, PartialEq)]
82pub enum DmaError {
83 ResetTimeout,
85}
86
87#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
89pub struct DmaInterruptStatus {
90 raw: u32,
91}
92
93impl DmaInterruptStatus {
94 pub const fn from_raw(raw: u32) -> Self {
96 Self { raw }
97 }
98
99 pub const fn raw(self) -> u32 {
101 self.raw
102 }
103
104 pub const fn has_tx_interrupt(self) -> bool {
106 self.raw & regs::bits::dmastatus::TI != 0
107 }
108
109 pub const fn has_tx_buffer_unavailable(self) -> bool {
111 self.raw & regs::bits::dmastatus::TU != 0
112 }
113
114 pub const fn has_tx_process_stopped(self) -> bool {
116 self.raw & regs::bits::dmastatus::TPS != 0
117 }
118
119 pub const fn has_tx_jabber_timeout(self) -> bool {
121 self.raw & regs::bits::dmastatus::TJT != 0
122 }
123
124 pub const fn has_rx_overflow(self) -> bool {
126 self.raw & regs::bits::dmastatus::OVF != 0
127 }
128
129 pub const fn has_tx_underflow(self) -> bool {
131 self.raw & regs::bits::dmastatus::UNF != 0
132 }
133
134 pub const fn has_rx_interrupt(self) -> bool {
136 self.raw & regs::bits::dmastatus::RI != 0
137 }
138
139 pub const fn has_rx_buffer_unavailable(self) -> bool {
141 self.raw & regs::bits::dmastatus::RU != 0
142 }
143
144 pub const fn has_rx_process_stopped(self) -> bool {
146 self.raw & regs::bits::dmastatus::RPS != 0
147 }
148
149 pub const fn has_rx_watchdog_timeout(self) -> bool {
151 self.raw & regs::bits::dmastatus::RWT != 0
152 }
153
154 pub const fn has_early_transmit_interrupt(self) -> bool {
156 self.raw & regs::bits::dmastatus::ETI != 0
157 }
158
159 pub const fn has_fatal_bus_error(self) -> bool {
161 self.raw & regs::bits::dmastatus::FBI != 0
162 }
163
164 pub const fn has_early_receive_interrupt(self) -> bool {
166 self.raw & regs::bits::dmastatus::ERI != 0
167 }
168
169 pub const fn is_normal_summary(self) -> bool {
171 self.raw & regs::bits::dmastatus::NIS != 0
172 }
173
174 pub const fn is_abnormal_summary(self) -> bool {
176 self.raw & regs::bits::dmastatus::AIS != 0
177 }
178
179 pub const fn error_bits(self) -> u32 {
181 (self.raw & regs::bits::dmastatus::EB_MASK) >> regs::bits::dmastatus::EB_SHIFT
182 }
183
184 pub const fn should_kick_rx(self) -> bool {
186 self.has_rx_interrupt()
187 || self.has_early_receive_interrupt()
188 || self.has_rx_overflow()
189 || self.has_rx_buffer_unavailable()
190 || self.has_rx_process_stopped()
191 || self.has_rx_watchdog_timeout()
192 }
193
194 pub const fn should_kick_tx(self) -> bool {
196 self.has_tx_interrupt()
197 || self.has_tx_buffer_unavailable()
198 || self.has_tx_underflow()
199 || self.has_tx_process_stopped()
200 || self.has_tx_jabber_timeout()
201 || self.has_early_transmit_interrupt()
202 }
203
204 pub const fn clear_mask(self) -> u32 {
206 self.raw
207 & (regs::bits::dmastatus::TI
208 | regs::bits::dmastatus::TPS
209 | regs::bits::dmastatus::TU
210 | regs::bits::dmastatus::TJT
211 | regs::bits::dmastatus::OVF
212 | regs::bits::dmastatus::UNF
213 | regs::bits::dmastatus::RI
214 | regs::bits::dmastatus::RU
215 | regs::bits::dmastatus::RPS
216 | regs::bits::dmastatus::RWT
217 | regs::bits::dmastatus::ETI
218 | regs::bits::dmastatus::FBI
219 | regs::bits::dmastatus::ERI
220 | regs::bits::dmastatus::AIS
221 | regs::bits::dmastatus::NIS)
222 }
223}
224
225pub struct Dma;
227
228impl Dma {
229 pub fn dma_reset() -> Result<(), DmaError> {
231 regs::write(regs::dma::BUS_MODE, regs::bits::dmabusmode::SWR);
232 #[cfg(test)]
233 regs::write(regs::dma::BUS_MODE, 0);
234 wait_for_dma_reset_clear(|| regs::read(regs::dma::BUS_MODE))
235 }
236
237 pub fn dma_init() {
239 regs::write(regs::dma::BUS_MODE, dma_bus_mode_value(DMA_BURST_LENGTH));
240 regs::write(regs::dma::OP_MODE, dma_op_mode_value());
241 regs::write(regs::dma::INT_EN, dma_interrupt_enable_value());
242 }
243
244 pub fn set_descriptor_lists(tx_descriptors: &[TDes], rx_descriptors: &[RDes]) {
246 regs::write(
247 regs::dma::TX_DESC_LIST,
248 tx_descriptors.as_ptr() as usize as u32,
249 );
250 regs::write(
251 regs::dma::RX_DESC_LIST,
252 rx_descriptors.as_ptr() as usize as u32,
253 );
254 }
255
256 pub fn demand_tx_poll() {
258 regs::write(regs::dma::TX_POLL_DEMAND, 1);
259 }
260
261 pub fn demand_rx_poll() {
263 regs::write(regs::dma::RX_POLL_DEMAND, 1);
264 }
265
266 pub fn read_interrupt_status() -> DmaInterruptStatus {
268 DmaInterruptStatus::from_raw(regs::read(regs::dma::STATUS))
269 }
270
271 pub fn clear_interrupt_status(status: DmaInterruptStatus) {
273 let clear_mask = status.clear_mask();
274 if clear_mask != 0 {
275 regs::write(regs::dma::STATUS, clear_mask);
276 }
277 }
278
279 pub fn disable_interrupts() {
281 regs::write(regs::dma::INT_EN, 0);
282 }
283
284 pub fn invalidate_descriptor<T>(descriptor: &T) {
286 unsafe {
290 Self::cache_invalidate(
291 descriptor as *const T as *const u8,
292 core::mem::size_of::<T>(),
293 );
294 }
295 }
296
297 pub fn invalidate_buffer_prefix(buffer: &[u8; BUF_SIZE], used: usize) {
299 let used = used.min(BUF_SIZE);
300 unsafe {
303 Self::cache_invalidate(buffer.as_ptr(), used);
304 }
305 }
306
307 pub unsafe fn peek_rdes(addr: u32) -> [u32; 4] {
314 Self::cache_invalidate(addr as *const u8, 16);
315 let p = addr as *const u32;
316 [
317 core::ptr::read_volatile(p),
318 core::ptr::read_volatile(p.offset(1)),
319 core::ptr::read_volatile(p.offset(2)),
320 core::ptr::read_volatile(p.offset(3)),
321 ]
322 }
323
324 pub unsafe fn peek_rdes_cached(addr: u32) -> [u32; 4] {
333 let p = addr as *const u32;
334 [
335 core::ptr::read_volatile(p),
336 core::ptr::read_volatile(p.offset(1)),
337 core::ptr::read_volatile(p.offset(2)),
338 core::ptr::read_volatile(p.offset(3)),
339 ]
340 }
341
342 pub unsafe fn cache_writeback(addr: *const u8, size: usize) {
347 let Some(range) = aligned_range(addr, size) else {
348 return;
349 };
350
351 sync_cache_region(CacheOp::Writeback, range);
352 }
353
354 pub unsafe fn cache_invalidate(addr: *const u8, size: usize) {
360 let Some(range) = aligned_range(addr, size) else {
361 return;
362 };
363
364 sync_cache_region(CacheOp::Invalidate, range);
365 }
366
367 pub fn flush_descriptor<T>(descriptor: &T) {
369 unsafe {
372 Self::cache_writeback(
373 descriptor as *const T as *const u8,
374 core::mem::size_of::<T>(),
375 );
376 }
377 }
378
379 pub fn flush_buffer(buffer: &[u8; BUF_SIZE]) {
381 unsafe {
384 Self::cache_writeback(buffer.as_ptr(), BUF_SIZE);
385 }
386 }
387
388 pub fn init_l2_cache_mode() {
400 #[cfg(target_arch = "riscv32")]
401 unsafe {
402 let set_mode: RomCacheSetL2Mode =
403 core::mem::transmute(ROM_CACHE_SET_L2_CACHE_MODE);
404 set_mode(CACHE_SIZE_256K, CACHE_8WAYS_ASSOC, CACHE_LINE_SIZE_64B);
405 let inv_all: RomCacheAll = core::mem::transmute(ROM_CACHE_INVALIDATE_ALL);
406 let _ = inv_all(CACHE_MAP_L2_CACHE);
407 }
408 }
409}
410
411fn aligned_range(addr: *const u8, size: usize) -> Option<CacheAlignedRange> {
412 if size == 0 {
413 return None;
414 }
415
416 let start = (addr as usize) & !CACHE_LINE_MASK;
417 let end = (addr as usize)
418 .checked_add(size)
419 .expect("cache sync range overflow")
420 .checked_add(CACHE_LINE_MASK)
421 .expect("cache sync range overflow")
422 & !CACHE_LINE_MASK;
423
424 Some(CacheAlignedRange {
425 start,
426 size: end - start,
427 })
428}
429
430fn wait_for_dma_reset_clear<F>(mut read_bus_mode: F) -> Result<(), DmaError>
431where
432 F: FnMut() -> u32,
433{
434 for _ in 0..DMA_RESET_TIMEOUT_POLLS {
435 if read_bus_mode() & regs::bits::dmabusmode::SWR == 0 {
436 return Ok(());
437 }
438 }
439
440 Err(DmaError::ResetTimeout)
441}
442
443fn dma_bus_mode_value(pbl: u32) -> u32 {
444 let pbl = pbl & 0x3f;
445
446 regs::bits::dmabusmode::AAL
447 | regs::bits::dmabusmode::FB
448 | regs::bits::dmabusmode::USP
449 | (pbl << regs::bits::dmabusmode::PBL_SHIFT)
450 | (pbl << regs::bits::dmabusmode::RPBL_SHIFT)
451}
452
453fn dma_op_mode_value() -> u32 {
454 regs::bits::dmaopmode::TSF | regs::bits::dmaopmode::FUF
458}
459
460fn dma_interrupt_enable_value() -> u32 {
461 regs::bits::dmainten::TIE
462 | regs::bits::dmainten::TSE
463 | regs::bits::dmainten::TUE
464 | regs::bits::dmainten::TJE
465 | regs::bits::dmainten::OVE
466 | regs::bits::dmainten::UNE
467 | regs::bits::dmainten::RIE
468 | regs::bits::dmainten::RUE
469 | regs::bits::dmainten::RSE
470 | regs::bits::dmainten::RWE
471 | regs::bits::dmainten::ETE
472 | regs::bits::dmainten::FBE
473 | regs::bits::dmainten::ERE
474 | regs::bits::dmainten::AIE
475 | regs::bits::dmainten::NIE
476}
477
478#[derive(Clone, Copy, Debug, Eq, PartialEq)]
481enum CacheOp {
482 Writeback,
483 Invalidate,
484}
485
486#[cfg(target_arch = "riscv32")]
489pub static LAST_WRITEBACK_RC: core::sync::atomic::AtomicI32 =
490 core::sync::atomic::AtomicI32::new(-999);
491#[cfg(target_arch = "riscv32")]
493pub static LAST_INVALIDATE_RC: core::sync::atomic::AtomicI32 =
494 core::sync::atomic::AtomicI32::new(-999);
495
496pub static CACHE_INV_TICKS: core::sync::atomic::AtomicU32 =
504 core::sync::atomic::AtomicU32::new(0);
505pub static CACHE_INV_CALLS: core::sync::atomic::AtomicU32 =
506 core::sync::atomic::AtomicU32::new(0);
507pub static CACHE_WB_TICKS: core::sync::atomic::AtomicU32 =
508 core::sync::atomic::AtomicU32::new(0);
509pub static CACHE_WB_CALLS: core::sync::atomic::AtomicU32 =
510 core::sync::atomic::AtomicU32::new(0);
511
512#[allow(dead_code)]
516#[inline]
517fn record_cache_call(op: CacheOp, dt_ticks: u32) {
518 use core::sync::atomic::Ordering::Relaxed;
519 match op {
520 CacheOp::Writeback => {
521 CACHE_WB_TICKS.fetch_add(dt_ticks, Relaxed);
522 CACHE_WB_CALLS.fetch_add(1, Relaxed);
523 }
524 CacheOp::Invalidate => {
525 CACHE_INV_TICKS.fetch_add(dt_ticks, Relaxed);
526 CACHE_INV_CALLS.fetch_add(1, Relaxed);
527 }
528 }
529}
530
531#[inline]
532fn sync_cache_region(_op: CacheOp, _range: CacheAlignedRange) {
533 #[cfg(target_arch = "riscv32")]
534 {
535 let t0 = crate::systimer::now_ticks() as u32;
539 let rc = critical_section::with(|_| unsafe {
544 fence(Ordering::SeqCst);
545 compiler_fence(Ordering::SeqCst);
546 let rc = match _op {
547 CacheOp::Writeback => {
548 let f: RomCacheAll = core::mem::transmute(ROM_CACHE_WRITEBACK_ALL);
556 let rc1 = f(CACHE_MAP_L1_DCACHE);
557 let rc2 = f(CACHE_MAP_L2_CACHE);
558 if rc1 != 0 {
559 rc1
560 } else {
561 rc2
562 }
563 }
564 CacheOp::Invalidate => {
565 let f: RomCacheOp = core::mem::transmute(ROM_CACHE_INVALIDATE_ADDR);
566 let rc1 = f(CACHE_MAP_L1_DCACHE, _range.start as u32, _range.size as u32);
567 let rc2 = f(CACHE_MAP_L2_CACHE, _range.start as u32, _range.size as u32);
568 if rc1 != 0 {
569 rc1
570 } else {
571 rc2
572 }
573 }
574 };
575 compiler_fence(Ordering::SeqCst);
576 fence(Ordering::SeqCst);
577 rc
578 });
579 let t1 = crate::systimer::now_ticks() as u32;
580 let dt = t1.wrapping_sub(t0);
581 match _op {
582 CacheOp::Writeback => {
583 LAST_WRITEBACK_RC.store(rc, core::sync::atomic::Ordering::Relaxed);
584 }
585 CacheOp::Invalidate => {
586 LAST_INVALIDATE_RC.store(rc, core::sync::atomic::Ordering::Relaxed);
587 }
588 }
589 record_cache_call(_op, dt);
590 }
591}
592
593#[cfg(test)]
594mod tests {
595 use super::{
596 aligned_range, dma_bus_mode_value, dma_interrupt_enable_value, dma_op_mode_value,
597 record_cache_call, wait_for_dma_reset_clear, CacheAlignedRange, CacheOp, Dma, DmaError,
598 DmaInterruptStatus, CACHE_INV_CALLS, CACHE_INV_TICKS, CACHE_LINE_SIZE, CACHE_WB_CALLS,
599 CACHE_WB_TICKS, DMA_BURST_LENGTH, DMA_RESET_TIMEOUT_POLLS,
600 };
601 use crate::{regs, zeroed_rx_descriptors, zeroed_tx_descriptors};
602 use core::sync::atomic::Ordering;
603 use std::sync::Mutex;
604
605 static CACHE_COUNTER_LOCK: Mutex<()> = Mutex::new(());
609
610 fn reset_cache_counters() {
611 CACHE_INV_TICKS.store(0, Ordering::Relaxed);
612 CACHE_INV_CALLS.store(0, Ordering::Relaxed);
613 CACHE_WB_TICKS.store(0, Ordering::Relaxed);
614 CACHE_WB_CALLS.store(0, Ordering::Relaxed);
615 }
616
617 #[test]
618 fn zero_size_range_is_skipped() {
619 assert_eq!(aligned_range(0x2000 as *const u8, 0), None);
620 }
621
622 #[test]
623 fn aligned_range_is_left_unchanged() {
624 assert_eq!(
625 aligned_range(0x2000 as *const u8, CACHE_LINE_SIZE),
626 Some(CacheAlignedRange {
627 start: 0x2000,
628 size: CACHE_LINE_SIZE,
629 })
630 );
631 }
632
633 #[test]
634 fn unaligned_range_is_expanded_to_cache_lines() {
635 assert_eq!(
637 aligned_range(0x2033 as *const u8, 80),
638 Some(CacheAlignedRange {
639 start: 0x2000,
640 size: 256,
641 })
642 );
643 }
644
645 #[test]
646 fn dma_reset_succeeds_when_swr_clears() {
647 let mut polls = 0usize;
648 let result = wait_for_dma_reset_clear(|| {
649 polls += 1;
650 if polls < 3 {
651 regs::bits::dmabusmode::SWR
652 } else {
653 0
654 }
655 });
656
657 assert_eq!(result, Ok(()));
658 assert_eq!(polls, 3);
659 }
660
661 #[test]
662 fn dma_reset_times_out_when_swr_stays_set() {
663 let mut polls = 0usize;
664 let result = wait_for_dma_reset_clear(|| {
665 polls += 1;
666 regs::bits::dmabusmode::SWR
667 });
668
669 assert_eq!(result, Err(DmaError::ResetTimeout));
670 assert_eq!(polls, DMA_RESET_TIMEOUT_POLLS);
671 }
672
673 #[test]
674 fn dma_reset_programs_software_reset_and_observes_clear_on_host() {
675 regs::reset_test_registers();
676
677 assert_eq!(Dma::dma_reset(), Ok(()));
678
679 assert_eq!(regs::read(regs::dma::BUS_MODE), 0);
680 }
681
682 #[test]
683 fn dma_bus_mode_enables_burst_and_alignment() {
684 let bus_mode = dma_bus_mode_value(DMA_BURST_LENGTH);
685
686 assert_ne!(bus_mode & regs::bits::dmabusmode::AAL, 0);
687 assert_ne!(bus_mode & regs::bits::dmabusmode::FB, 0);
688 assert_ne!(bus_mode & regs::bits::dmabusmode::USP, 0);
689 assert_eq!(
690 bus_mode & regs::bits::dmabusmode::PBL_MASK,
691 DMA_BURST_LENGTH << regs::bits::dmabusmode::PBL_SHIFT
692 );
693 assert_eq!(
694 bus_mode & regs::bits::dmabusmode::RPBL_MASK,
695 DMA_BURST_LENGTH << regs::bits::dmabusmode::RPBL_SHIFT
696 );
697 assert_eq!(bus_mode & regs::bits::dmabusmode::DSL_MASK, 0);
698 }
699
700 #[test]
701 fn dma_op_mode_uses_tx_store_forward_and_cut_through_rx() {
702 let op_mode = dma_op_mode_value();
703
704 assert_eq!(
705 op_mode,
706 regs::bits::dmaopmode::TSF | regs::bits::dmaopmode::FUF
707 );
708 assert_eq!(op_mode & regs::bits::dmaopmode::RSF, 0,
709 "RSF must stay 0 on P4 — RX FIFO too small for full frames");
710 }
711
712 #[test]
713 fn dma_interrupt_enable_value_covers_normal_and_abnormal_paths() {
714 let inten = dma_interrupt_enable_value();
715
716 assert_ne!(inten & regs::bits::dmainten::TIE, 0);
717 assert_ne!(inten & regs::bits::dmainten::RIE, 0);
718 assert_ne!(inten & regs::bits::dmainten::OVE, 0);
719 assert_ne!(inten & regs::bits::dmainten::UNE, 0);
720 assert_ne!(inten & regs::bits::dmainten::FBE, 0);
721 assert_ne!(inten & regs::bits::dmainten::AIE, 0);
722 assert_ne!(inten & regs::bits::dmainten::NIE, 0);
723 }
724
725 #[test]
726 fn dma_init_programs_bus_op_mode_and_interrupt_mask() {
727 regs::reset_test_registers();
728
729 Dma::dma_init();
730
731 assert_eq!(
732 regs::read(regs::dma::BUS_MODE),
733 dma_bus_mode_value(DMA_BURST_LENGTH)
734 );
735 assert_eq!(regs::read(regs::dma::OP_MODE), dma_op_mode_value());
736 assert_eq!(regs::read(regs::dma::INT_EN), dma_interrupt_enable_value());
737 }
738
739 #[test]
740 fn dma_descriptor_lists_are_programmed_from_slice_addresses() {
741 regs::reset_test_registers();
742 let tx_descriptors = zeroed_tx_descriptors();
743 let rx_descriptors = zeroed_rx_descriptors();
744
745 Dma::set_descriptor_lists(&tx_descriptors, &rx_descriptors);
746
747 assert_eq!(
748 regs::read(regs::dma::TX_DESC_LIST),
749 tx_descriptors.as_ptr() as usize as u32
750 );
751 assert_eq!(
752 regs::read(regs::dma::RX_DESC_LIST),
753 rx_descriptors.as_ptr() as usize as u32
754 );
755 }
756
757 #[test]
758 fn dma_poll_demands_and_interrupt_disable_update_registers() {
759 regs::reset_test_registers();
760 regs::write(regs::dma::INT_EN, u32::MAX);
761
762 Dma::demand_tx_poll();
763 Dma::demand_rx_poll();
764 Dma::disable_interrupts();
765
766 assert_eq!(regs::read(regs::dma::TX_POLL_DEMAND), 1);
767 assert_eq!(regs::read(regs::dma::RX_POLL_DEMAND), 1);
768 assert_eq!(regs::read(regs::dma::INT_EN), 0);
769 }
770
771 #[test]
772 fn dma_interrupt_status_extracts_recovery_flags() {
773 let status = DmaInterruptStatus::from_raw(
774 regs::bits::dmastatus::OVF
775 | regs::bits::dmastatus::RU
776 | regs::bits::dmastatus::UNF
777 | regs::bits::dmastatus::FBI
778 | regs::bits::dmastatus::AIS
779 | regs::bits::dmastatus::NIS
780 | (0b111 << regs::bits::dmastatus::EB_SHIFT),
781 );
782
783 assert!(status.has_rx_overflow());
784 assert!(status.has_rx_buffer_unavailable());
785 assert!(status.has_tx_underflow());
786 assert!(status.has_fatal_bus_error());
787 assert!(status.should_kick_rx());
788 assert!(status.should_kick_tx());
789 assert!(status.is_abnormal_summary());
790 assert!(status.is_normal_summary());
791 assert_eq!(status.error_bits(), 0b111);
792 }
793
794 #[test]
795 fn dma_interrupt_status_clear_mask_does_not_touch_state_fields() {
796 let status = DmaInterruptStatus::from_raw(
797 regs::bits::dmastatus::TI
798 | regs::bits::dmastatus::OVF
799 | regs::bits::dmastatus::AIS
800 | regs::bits::dmastatus::NIS
801 | (0b101 << regs::bits::dmastatus::RS_SHIFT)
802 | (0b110 << regs::bits::dmastatus::TS_SHIFT)
803 | (0b011 << regs::bits::dmastatus::EB_SHIFT),
804 );
805
806 assert_eq!(
807 status.clear_mask(),
808 regs::bits::dmastatus::TI
809 | regs::bits::dmastatus::OVF
810 | regs::bits::dmastatus::AIS
811 | regs::bits::dmastatus::NIS
812 );
813 }
814
815 #[test]
816 fn dma_clear_interrupt_status_writes_only_clearable_bits() {
817 regs::reset_test_registers();
818 let raw = regs::bits::dmastatus::RI
819 | regs::bits::dmastatus::AIS
820 | (0b101 << regs::bits::dmastatus::RS_SHIFT);
821 let status = DmaInterruptStatus::from_raw(raw);
822
823 Dma::clear_interrupt_status(status);
824
825 assert_eq!(
826 regs::read(regs::dma::STATUS),
827 regs::bits::dmastatus::RI | regs::bits::dmastatus::AIS
828 );
829 }
830
831 #[test]
832 fn dma_clear_interrupt_status_skips_zero_clear_mask() {
833 regs::reset_test_registers();
834 regs::write(regs::dma::STATUS, 0xDEAD_BEEF);
835 let status = DmaInterruptStatus::from_raw(0b101 << regs::bits::dmastatus::RS_SHIFT);
836
837 Dma::clear_interrupt_status(status);
838
839 assert_eq!(regs::read(regs::dma::STATUS), 0xDEAD_BEEF);
840 }
841
842 #[test]
843 fn dma_interrupt_status_clear_mask_covers_all_sticky_bits() {
844 let clearable = regs::bits::dmastatus::TI
845 | regs::bits::dmastatus::TPS
846 | regs::bits::dmastatus::TU
847 | regs::bits::dmastatus::TJT
848 | regs::bits::dmastatus::OVF
849 | regs::bits::dmastatus::UNF
850 | regs::bits::dmastatus::RI
851 | regs::bits::dmastatus::RU
852 | regs::bits::dmastatus::RPS
853 | regs::bits::dmastatus::RWT
854 | regs::bits::dmastatus::ETI
855 | regs::bits::dmastatus::FBI
856 | regs::bits::dmastatus::ERI
857 | regs::bits::dmastatus::AIS
858 | regs::bits::dmastatus::NIS;
859 let state_fields = (0b101 << regs::bits::dmastatus::RS_SHIFT)
860 | (0b110 << regs::bits::dmastatus::TS_SHIFT)
861 | (0b011 << regs::bits::dmastatus::EB_SHIFT);
862
863 let status = DmaInterruptStatus::from_raw(clearable | state_fields);
864
865 assert_eq!(status.clear_mask(), clearable);
866 }
867
868 #[test]
869 fn record_cache_call_invalidate_increments_only_invalidate_counters() {
870 let _g = CACHE_COUNTER_LOCK.lock().unwrap();
871 reset_cache_counters();
872
873 record_cache_call(CacheOp::Invalidate, 100);
874
875 assert_eq!(CACHE_INV_TICKS.load(Ordering::Relaxed), 100);
876 assert_eq!(CACHE_INV_CALLS.load(Ordering::Relaxed), 1);
877 assert_eq!(CACHE_WB_TICKS.load(Ordering::Relaxed), 0);
878 assert_eq!(CACHE_WB_CALLS.load(Ordering::Relaxed), 0);
879 }
880
881 #[test]
882 fn record_cache_call_writeback_increments_only_writeback_counters() {
883 let _g = CACHE_COUNTER_LOCK.lock().unwrap();
884 reset_cache_counters();
885
886 record_cache_call(CacheOp::Writeback, 200);
887
888 assert_eq!(CACHE_WB_TICKS.load(Ordering::Relaxed), 200);
889 assert_eq!(CACHE_WB_CALLS.load(Ordering::Relaxed), 1);
890 assert_eq!(CACHE_INV_TICKS.load(Ordering::Relaxed), 0);
891 assert_eq!(CACHE_INV_CALLS.load(Ordering::Relaxed), 0);
892 }
893
894 #[test]
895 fn record_cache_call_accumulates_across_multiple_calls() {
896 let _g = CACHE_COUNTER_LOCK.lock().unwrap();
897 reset_cache_counters();
898
899 record_cache_call(CacheOp::Invalidate, 50);
900 record_cache_call(CacheOp::Invalidate, 75);
901 record_cache_call(CacheOp::Writeback, 1000);
902 record_cache_call(CacheOp::Invalidate, 25);
903 record_cache_call(CacheOp::Writeback, 500);
904
905 assert_eq!(CACHE_INV_TICKS.load(Ordering::Relaxed), 150);
906 assert_eq!(CACHE_INV_CALLS.load(Ordering::Relaxed), 3);
907 assert_eq!(CACHE_WB_TICKS.load(Ordering::Relaxed), 1500);
908 assert_eq!(CACHE_WB_CALLS.load(Ordering::Relaxed), 2);
909 }
910
911 #[test]
915 fn record_cache_call_handles_systimer_wraparound() {
916 let _g = CACHE_COUNTER_LOCK.lock().unwrap();
917 reset_cache_counters();
918
919 let t0: u32 = u32::MAX - 10;
921 let t1: u32 = 50; let dt = t1.wrapping_sub(t0);
923 assert_eq!(dt, 61, "wrapping_sub across u32 boundary must give 61");
924
925 record_cache_call(CacheOp::Invalidate, dt);
926 assert_eq!(CACHE_INV_TICKS.load(Ordering::Relaxed), 61);
927 }
928
929 #[test]
933 fn record_cache_call_counters_wrap_cleanly_on_overflow() {
934 let _g = CACHE_COUNTER_LOCK.lock().unwrap();
935 reset_cache_counters();
936
937 CACHE_INV_TICKS.store(u32::MAX - 100, Ordering::Relaxed);
938 record_cache_call(CacheOp::Invalidate, 200);
939
940 assert_eq!(CACHE_INV_TICKS.load(Ordering::Relaxed), 99);
942 }
943}