diff --git a/src/cpu/mips_cache_v2.rs b/src/cpu/mips_cache_v2.rs index 62383e50..362dc580 100644 --- a/src/cpu/mips_cache_v2.rs +++ b/src/cpu/mips_cache_v2.rs @@ -1375,6 +1375,10 @@ impl usize { + let half = 1usize << (lines_shift - 1); + (((addr >> line_shift) as usize) & (half - 1)) | ((addr as usize & 1) << (lines_shift - 1)) + } + + /// The same for an R10000 Index_Load/Store_Data: a doubleword slot in a + /// data array of `slots` doublewords. + #[inline] + fn r10k_way_slot(addr: u64, slots: usize) -> usize { + let half = slots / 2; + ((addr as usize >> 3) & (half - 1)) | ((addr as usize & 1) * half) + } + fn pidx(&self, virt_addr: u64) -> u32 { ((virt_addr >> L2_PIDX_VADDR_SHIFT) & L2_PIDX_VADDR_MASK as u64) as u32 } @@ -2001,6 +2022,46 @@ impl "R5000", model::R10000 => "R10000", @@ -3973,20 +4044,17 @@ impl return 0, C_R10K_ILD if matches!(sel, CACH_PI | CACH_PD | CACH_SD) => { let is_l2 = sel == CACH_SD; - // The index is a byte offset into the data array. Bit 0 - // selects the way on real silicon; this model is - // direct-mapped, and bit 0 falls below the u64 slot index, - // so it drops out without any special case. - let slot = (virt_addr as usize) >> 3; + // The index is a byte offset into one way's data, and bit + // 0 selects the way: the same folding as the tag ops + // below, so data and tags name the same line. if is_l2 && HAS_L2 { - return self.l2.data()[slot & (L2_DATA - 1)]; + return self.l2.data()[Self::r10k_way_slot(virt_addr, L2_DATA)]; } - return self.dc.data()[slot & (DC_DATA - 1)]; + return self.dc.data()[Self::r10k_way_slot(virt_addr, DC_DATA)]; } C_R10K_ISD if matches!(sel, CACH_SI | CACH_SD) => { - let slot = (virt_addr as usize) >> 3; if HAS_L2 { - self.l2.data_mut()[slot & (L2_DATA - 1)] = phys_addr; + self.l2.data_mut()[Self::r10k_way_slot(virt_addr, L2_DATA)] = phys_addr; } return 0; } @@ -4027,8 +4095,24 @@ impl> 14) as usize & 1) << Self::IC_NUM_LINES_SHIFT) } else { set } @@ -4152,9 +4236,6 @@ impl> 13) & L2_PTAG_MASK; let state = (tag_lo >> 10) & 0x7; @@ -5262,6 +5343,69 @@ mod tests { // Flush a single L2 set to memory. `phys` is any address within the L2 line. // Only valid when L2 is present (r4k or r5k+r5ksc). + /// A whole-cache flush the way an R10000 kernel does it -- every index of + /// one way, then of the other, bit 0 picking the way -- reaches every line, + /// including those this direct-mapped model keeps in its upper half. + #[test] + fn r10000_index_flush_reaches_both_ways() { + let mem = Arc::new(Memory::new(MEM_MB)); + let cache: R10000Cache = make_cache_of(mem.clone()); + // Upper half of the L1D (VA bit 14) and of the 1 MB L2 (PA bit 19). + let cases = [(0x9800_0000_0000_6040u64, 0x2040u32), (0x9800_0000_0008_2080, 0x8_2080)]; + for (i, &(va, pa)) in cases.iter().enumerate() { + let _ = cache.write::<4>(va, pa as u64, 0xa5a5_0000 + i as u64); + } + for va in (0..16 * 1024u64).step_by(32) { + for way in 0..2 { + cache.cache_op(C_IWBINV | CACH_PD, 0x8000_0000 | va | way, 0); + } + } + for va in (0..512 * 1024u64).step_by(128) { + for way in 0..2 { + let a = 0x8000_0000 | va | way; + cache.cache_op(C_IWBINV | CACH_SD, a, a); + } + } + for (i, &(_, pa)) in cases.iter().enumerate() { + assert_eq!(mem_read(&mem, pa), 0xa5a5_0000 + i as u32, "line at {pa:#x} not flushed"); + } + } + + /// One physical line used under two virtual colours (index bits 12-13 + /// differ): the R4400 raises a virtual coherency exception on the + /// secondary hit; the R10000 resolves it itself, and a store under one + /// colour is what a load under the other sees. + #[test] + fn the_r10000_resolves_a_colour_mismatch_where_the_r4400_raises_vce() { + let pa = 0x8040u64; + let (va1, va2) = (0x0010_0040u64, 0x0010_3040u64); + + let mem = Arc::new(Memory::new(MEM_MB)); + mem_write(&mem, pa as u32, 0x1111_2222); + let r4k: R4400Cache = make_cache_of(mem.clone()); + assert_eq!(r4k.read::<4>(va1, pa).status, BUS_OK); + assert_eq!(r4k.read::<4>(va2, pa).status, BUS_VCE, "the R4400 raises VCE"); + + let mem = Arc::new(Memory::new(MEM_MB)); + mem_write(&mem, pa as u32, 0x1111_2222); + let r10k: R10000Cache = make_cache_of(mem.clone()); + assert_eq!(r10k.read::<4>(va1, pa).data, 0x1111_2222); + let r = r10k.read::<4>(va2, pa); + assert_eq!((r.status, r.data), (BUS_OK, 0x1111_2222), "the R10000 carries on"); + assert_eq!(r10k.write::<4>(va2, pa, 0x3333_4444), BUS_OK); + assert_eq!(r10k.read::<4>(va1, pa).data, 0x3333_4444, "the old colour's copy was dropped"); + } + + /// The R10000 implements 44 virtual address bits; XContext's fields are + /// laid out around them, so the full model must say so too. + #[test] + fn the_r10000_full_model_has_44_va_bits() { + assert_eq!(::VA_BITS, 44); + assert_eq!(::VA_BITS, 44); + assert_eq!(::VA_BITS, 40); + assert_eq!(::VA_BITS, 40); + } + fn flush_l2_to_mem(cache: &R4400Cache, phys: u32) { let l2_set = (phys as usize >> R4400Cache::L2_LINE_SHIFT as usize) & (R4400Cache::L2_SIZE / R4400Cache::L2_LINE - 1); let lp = (l2_set * R4400Cache::L2_LINE) as u64;