psx_rt/cache.rs
1//! Instruction-cache invalidation, done directly on the hardware.
2//!
3//! The runtime does this itself rather than through the BIOS `A(44h)
4//! FlushCache` service, so its boot path makes no BIOS call.
5//!
6//! The R3000A's instruction cache is 4 KiB, direct mapped, 256 lines of
7//! 16 bytes. psx-spx ("Memory Control", FFFE0130h, and the COP0 status
8//! register's IsC bit 16) describes how software reaches its tags: with
9//! the cache control register in tag test mode and SR.IsC set, a word
10//! store to an address in `0x000..0x1000` writes the tag of the line that
11//! address maps to, instead of memory. Storing zero marks the line
12//! invalid.
13//!
14//! The routine below:
15//!
16//! - runs from the KSEG1 (uncached) alias of its own code, so no
17//! instruction is fetched through the cache while it changes;
18//! - clears SR before touching the cache control register, so no
19//! interrupt can run while the scratchpad is unmapped or the cache is
20//! isolated;
21//! - selects `cache_control::FLUSH`, sets SR.IsC alone, and zeroes the
22//! tag of every line;
23//! - clears SR.IsC before writing `cache_control::RUNNING` back, since
24//! whether a store to FFFE0130h reaches the port while the cache is
25//! isolated isn't documented;
26//! - puts the caller's SR back and returns.
27
28#[cfg(target_arch = "mips")]
29use psx_hw::memory::cache_control;
30
31// O32: uses only $t0-$t6 (caller saved), never the stack, returns
32// through $ra. SR is COP0 register 12.
33#[cfg(target_arch = "mips")]
34core::arch::global_asm!(
35 r#"
36 .set noreorder
37 .section .text.__psx_rt_flush_i_cache,"ax",@progbits
38 .globl __psx_rt_flush_i_cache
39 .type __psx_rt_flush_i_cache,@function
40 .p2align 2
41__psx_rt_flush_i_cache:
42 lui $t0, %hi(.Lpsx_rt_flush_uncached)
43 addiu $t0, $t0, %lo(.Lpsx_rt_flush_uncached)
44 lui $t1, 0xA000
45 or $t0, $t0, $t1
46 jr $t0
47 nop
48.Lpsx_rt_flush_uncached:
49 mfc0 $t2, $12
50 lui $t3, {port_hi}
51 mtc0 $zero, $12
52 lui $t4, {flush_hi}
53 ori $t4, $t4, {flush_lo}
54 sw $t4, {port_lo}($t3)
55 lui $t4, 0x0001
56 mtc0 $t4, $12
57 nop
58 nop
59 addu $t5, $zero, $zero
60 li $t6, 0x1000
61.Lpsx_rt_flush_line:
62 addiu $t5, $t5, 16
63 bne $t5, $t6, .Lpsx_rt_flush_line
64 sw $zero, -16($t5)
65 mtc0 $zero, $12
66 nop
67 nop
68 lui $t4, {running_hi}
69 ori $t4, $t4, {running_lo}
70 sw $t4, {port_lo}($t3)
71 mtc0 $t2, $12
72 nop
73 jr $ra
74 nop
75 .size __psx_rt_flush_i_cache, . - __psx_rt_flush_i_cache
76 .set reorder
77 "#,
78 port_hi = const cache_control::ADDR >> 16,
79 port_lo = const cache_control::ADDR & 0xFFFF,
80 flush_hi = const cache_control::FLUSH >> 16,
81 flush_lo = const cache_control::FLUSH & 0xFFFF,
82 running_hi = const cache_control::RUNNING >> 16,
83 running_lo = const cache_control::RUNNING & 0xFFFF,
84);
85
86#[cfg(target_arch = "mips")]
87extern "C" {
88 fn __psx_rt_flush_i_cache();
89}
90
91/// Invalidate every line of the instruction cache.
92///
93/// Call it after writing instructions to RAM (an exception vector,
94/// patched or loaded code) so the CPU fetches the new words. Interrupts
95/// are off for the whole time the cache is being changed.
96#[cfg(target_arch = "mips")]
97#[inline(always)]
98#[doc(alias = "FlushCache")]
99pub fn flush_instruction_cache() {
100 // SAFETY: the routine above clobbers only O32 caller-saved registers
101 // ($t0-$t6), uses no stack, returns through $ra, and leaves SR and the
102 // cache control register as it found them (SR) or in the running
103 // setting (cache control). Its only stores land on isolated cache
104 // tags or the cache control port, never on RAM.
105 unsafe { __psx_rt_flush_i_cache() }
106}
107
108/// Host no-op, so shared code can call it unconditionally.
109#[cfg(not(target_arch = "mips"))]
110#[doc(alias = "FlushCache")]
111pub fn flush_instruction_cache() {}
112
113/// Renamed to [`flush_instruction_cache`].
114#[deprecated(note = "renamed to `flush_instruction_cache`")]
115#[inline(always)]
116pub fn flush_i_cache() {
117 flush_instruction_cache()
118}