aarch64_cpu_ext/cache.rs
1use core::arch::asm;
2
3use aarch64_cpu::{
4 asm::barrier::{NSH, SY, dsb, isb},
5 registers::*,
6};
7
8use crate::asm::cache::{CISW, CIVAC, CSW, CVAC, IALLU, ISW, IVAC, dc, ic};
9
10pub fn icache_flush_all() {
11 ic(IALLU);
12 dsb(NSH);
13 isb(SY);
14}
15
16#[repr(C)]
17#[derive(Debug, Clone, Copy)]
18pub enum CacheOp {
19 /// Write back to memory
20 Clean,
21 /// Invalidate cache
22 Invalidate,
23 /// Clean and invalidate
24 CleanAndInvalidate,
25}
26
27#[inline(always)]
28pub fn cache_line_size() -> usize {
29 unsafe {
30 let mut ctr_el0: u64;
31 asm!("mrs {}, ctr_el0", out(reg) ctr_el0);
32 // CTR_EL0.DminLine (bits 19:16) - log2 of the number of words in the smallest cache line
33 let log2_cache_line_size = ((ctr_el0 >> 16) & 0xF) as usize;
34 // Calculate the cache line size: 4 * (2^log2_cache_line_size) bytes
35 4 << log2_cache_line_size
36 }
37}
38
39/// Performs a cache operation on a single cache line.
40#[inline]
41fn _dcache_line(op: CacheOp, addr: usize) {
42 let addr = addr as u64;
43 match op {
44 CacheOp::Clean => dc(CVAC, addr),
45 CacheOp::Invalidate => dc(IVAC, addr),
46 CacheOp::CleanAndInvalidate => dc(CIVAC, addr),
47 }
48}
49
50/// Performs a cache operation on a range of memory.
51#[inline]
52pub fn dcache_range(op: CacheOp, addr: usize, size: usize) {
53 let start = addr;
54 let end = start + size;
55 let cache_line_size = cache_line_size();
56
57 let mut aligned_addr = addr & !(cache_line_size - 1);
58
59 while aligned_addr < end {
60 _dcache_line(op, aligned_addr);
61 aligned_addr += cache_line_size;
62 }
63
64 dsb(SY);
65 isb(SY);
66}
67
68/// Performs a cache operation on a value.
69pub fn dcache_value<T>(op: CacheOp, v: &T) {
70 // Get the pointer to the value
71 let ptr = v as *const T as usize;
72 // Calculate the size of the value in bytes
73 let size = core::mem::size_of_val(v);
74 // Perform cache operation on the value
75 dcache_range(op, ptr, size);
76}
77
78/// Performs a cache operation on a cache level.
79/// https://developer.arm.com/documentation/ddi0601/2024-09/AArch64-Instructions/DC-CISW--Data-or-unified-Cache-line-Clean-and-Invalidate-by-Set-Way
80/// https://developer.arm.com/documentation/ddi0601/2024-09/AArch64-Registers/CTR-EL0--Cache-Type-Register?lang=en
81/// https://developer.arm.com/documentation/ddi0601/2024-09/AArch64-Registers/CCSIDR-EL1--Current-Cache-Size-ID-Register?lang=en
82/// https://github.com/u-boot/u-boot/blob/master/arch/arm/cpu/armv8/cache.S
83///
84/// DC instruction set/way format:
85/// - Bits [63:32]: Reserved, RES0
86/// - Bits [31:4]: SetWay field containing:
87/// - Way field: bits[31:32-A] where A = Log2(ASSOCIATIVITY)
88/// - Set field: bits[B-1:L] where B = L + S, L = Log2(LINELEN), S = Log2(NSETS)
89/// - Bits[L-1:4]: RES0
90/// - Bits [3:1]: Level (cache level minus 1)
91/// - Bit [0]: Reserved, RES0
92#[inline]
93fn dcache_level(op: CacheOp, level: u64) {
94 assert!(level < 8, "armv8 level range is 0-7");
95
96 isb(SY);
97 CSSELR_EL1.write(CSSELR_EL1::InD::Data + CSSELR_EL1::Level.val(level));
98 isb(SY);
99
100 // Read cache parameters from CCSIDR_EL1
101 // Note: All values from CCSIDR_EL1 need to be adjusted according to ARM spec:
102 // - LineSize: (Log2(bytes in cache line)) - 4
103 // - Associativity: (Associativity of cache) - 1
104 // - NumSets: (Number of sets in cache) - 1
105 let line_size_raw = CCSIDR_EL1.read(CCSIDR_EL1::LineSize) as u32;
106 let associativity_raw = CCSIDR_EL1.read(CCSIDR_EL1::AssociativityWithCCIDX) as u32;
107 let num_sets_raw = CCSIDR_EL1.read(CCSIDR_EL1::NumSetsWithCCIDX) as u32;
108
109 // Convert raw values to actual values
110 let line_size_log2_bytes = line_size_raw + 4; // Actual log2 of line size in bytes
111 let associativity = associativity_raw + 1; // Actual associativity
112 let num_sets = num_sets_raw + 1; // Actual number of sets
113
114 // Calculate bit positions for set/way encoding according to ARM spec:
115 // L = Log2(LINELEN) where LINELEN is line length in bytes
116 // S = Log2(NSETS)
117 // A = Log2(ASSOCIATIVITY)
118 // Way field: bits[31:32-A]
119 // Set field: bits[B-1:L] where B = L + S
120
121 let l = line_size_log2_bytes; // Log2 of line length in bytes
122
123 // Calculate the number of bits needed to represent the way index
124 // leading_zeros on (associativity-1) gives us the position of the MSB needed
125 let way_shift = associativity_raw.leading_zeros(); // Way field starts at bit (32-A)
126 let set_shift = l; // Set field starts at bit L (line size offset)
127
128 // Loop over all sets and ways (0-based indexing for hardware)
129 for set in 0..num_sets {
130 for way in 0..associativity {
131 // Construct the set/way value according to ARM DC instruction format:
132 // Way field: bits[31:32-A] - way value shifted to proper bit position
133 // Set field: bits[B-1:L] - set value shifted to proper bit position
134 //
135 // Example: If associativity=4, way indices are 0,1,2,3
136 // We need A=2 bits (Log2(4)=2), so way field is at bits[31:30]
137 // way_shift = 32 - 2 = 30, so way values are shifted left by 30 bits
138 let set_way = (way << way_shift) | (set << set_shift);
139
140 // Complete operand: set_way in bits [31:4], level in bits [3:1], bit [0] is RES0
141 let cisw = (set_way as u64) | (level << 1);
142 match op {
143 CacheOp::Invalidate => dc(ISW, cisw),
144 CacheOp::Clean => dc(CSW, cisw),
145 CacheOp::CleanAndInvalidate => dc(CISW, cisw),
146 }
147 }
148 }
149}
150
151/// Performs a cache operation on all memory.
152pub fn dcache_all(op: CacheOp) {
153 let clidr = CLIDR_EL1.get();
154
155 for level in 0..8 {
156 let ty = (clidr >> (level * 3)) & 0b111;
157
158 // Cache type values:
159 // 0b000 = No cache
160 // 0b001 = Instruction cache only
161 // 0b010 = Data cache only
162 // 0b011 = Separate instruction and data caches
163 // 0b100 = Unified cache
164 // Only process data caches (0b010) and unified caches (0b100)
165 // or separate I+D caches (0b011) - for 0b011, we process the data cache
166 match ty {
167 0b000 => return, // No cache at this level, we're done
168 0b001 => continue, // Instruction cache only, skip
169 0b010..=0b100 => {
170 // Data cache (0b010), separate I+D caches (0b011), or unified cache (0b100) - process it
171 dcache_level(op, level);
172 }
173 _ => continue, // Reserved values, skip
174 }
175 }
176 dsb(SY);
177 isb(SY);
178}