xref: /xnu-8020.121.3/osfmk/arm64/tlb.h (revision fdd8201d7b966f0c3ea610489d29bd841d358941)
1 /*
2  * Copyright (c) 2019 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #pragma once
30 
31 #include <arm64/proc_reg.h>
32 #include <machine/atomic.h>
33 
34 #define tlbi_addr(x) ((((x) >> 12) & TLBI_ADDR_MASK) << TLBI_ADDR_SHIFT)
35 #define tlbi_asid(x) (((uintptr_t)(x) & TLBI_ASID_MASK) << TLBI_ASID_SHIFT)
36 
37 #if __ARM_KERNEL_PROTECT__
38 /*
39  * __ARM_KERNEL_PROTECT__ adds two complications to TLB management:
40  *
41  * 1. As each pmap has two ASIDs, every TLB operation that targets an ASID must
42  *   target both ASIDs for the pmap that owns the target ASID.
43  *
44  * 2. Any TLB operation targeting the kernel_pmap ASID (ASID 0) must target all
45  *   ASIDs (as kernel_pmap mappings may be referenced while using an ASID that
46  *   belongs to another pmap).  We expect these routines to be called with the
47  *   EL0 ASID for the target; not the EL1 ASID.
48  */
49 #endif /* __ARM_KERNEL_PROTECT__ */
50 
51 static inline void
sync_tlb_flush(void)52 sync_tlb_flush(void)
53 {
54 	__builtin_arm_dsb(DSB_ISH);
55 	__builtin_arm_isb(ISB_SY);
56 }
57 
58 static inline void
sync_tlb_flush_local(void)59 sync_tlb_flush_local(void)
60 {
61 	__builtin_arm_dsb(DSB_NSH);
62 	__builtin_arm_isb(ISB_SY);
63 }
64 
65 // flush_mmu_tlb: full TLB flush on all cores
66 static inline void
flush_mmu_tlb_async(void)67 flush_mmu_tlb_async(void)
68 {
69 	asm volatile ("tlbi vmalle1is");
70 }
71 
72 static inline void
flush_mmu_tlb(void)73 flush_mmu_tlb(void)
74 {
75 	flush_mmu_tlb_async();
76 	sync_tlb_flush();
77 }
78 
79 // flush_core_tlb: full TLB flush on local core only
80 static inline void
flush_core_tlb_async(void)81 flush_core_tlb_async(void)
82 {
83 	asm volatile ("tlbi vmalle1");
84 }
85 
86 static inline void
flush_core_tlb(void)87 flush_core_tlb(void)
88 {
89 	flush_core_tlb_async();
90 	sync_tlb_flush_local();
91 }
92 
93 // flush_mmu_tlb_allentries_async: flush entries that map VA range, all ASIDS, all cores
94 // start and end are in units of 4K pages.
95 static inline void
flush_mmu_tlb_allentries_async(uint64_t start,uint64_t end,uint64_t pmap_page_size,bool last_level_only)96 flush_mmu_tlb_allentries_async(uint64_t start, uint64_t end, uint64_t pmap_page_size, bool last_level_only)
97 {
98 #if __ARM_16K_PG__
99 	if (pmap_page_size == 16384) {
100 		start = start & ~0x3ULL;
101 
102 		/*
103 		 * The code below is not necessarily correct.  From an overview of
104 		 * the client code, the expected contract for TLB flushes is that
105 		 * we will expand from an "address, length" pair to "start address,
106 		 * end address" in the course of a TLB flush.  This suggests that
107 		 * a flush for "X, X+4" is actually only asking for a flush of a
108 		 * single 16KB page.  At the same time, we'd like to be prepared
109 		 * for bad inputs (X, X+3), so add 3 and then truncate the 4KB page
110 		 * number to a 16KB page boundary.  This should deal correctly with
111 		 * unaligned inputs.
112 		 *
113 		 * If our expecations about client behavior are wrong however, this
114 		 * will lead to occasional TLB corruption on platforms with 16KB
115 		 * pages.
116 		 */
117 		end = (end + 0x3ULL) & ~0x3ULL;
118 	}
119 #endif // __ARM_16K_PG__
120 	if (last_level_only) {
121 		for (; start < end; start += (pmap_page_size / 4096)) {
122 			asm volatile ("tlbi vaale1is, %0" : : "r"(start));
123 		}
124 	} else {
125 		for (; start < end; start += (pmap_page_size / 4096)) {
126 			asm volatile ("tlbi vaae1is, %0" : : "r"(start));
127 		}
128 	}
129 }
130 
131 static inline void
flush_mmu_tlb_allentries(uint64_t start,uint64_t end,uint64_t pmap_page_size,bool last_level_only)132 flush_mmu_tlb_allentries(uint64_t start, uint64_t end, uint64_t pmap_page_size, bool last_level_only)
133 {
134 	flush_mmu_tlb_allentries_async(start, end, pmap_page_size, last_level_only);
135 	sync_tlb_flush();
136 }
137 
138 // flush_mmu_tlb_entry: flush TLB entries that map a VA and ASID, all cores
139 // Will also flush global entries that match the VA
140 static inline void
flush_mmu_tlb_entry_async(uint64_t val)141 flush_mmu_tlb_entry_async(uint64_t val)
142 {
143 #if __ARM_KERNEL_PROTECT__
144 	uint64_t asid = val >> TLBI_ASID_SHIFT;
145 	if (asid == 0) {
146 		asm volatile ("tlbi vaae1is, %0" : : "r"(val));
147 		return;
148 	}
149 	val = val & ~(1ULL << TLBI_ASID_SHIFT);
150 	asm volatile ("tlbi vae1is, %0" : : "r"(val));
151 	val = val | (1ULL << TLBI_ASID_SHIFT);
152 #endif /* __ARM_KERNEL_PROTECT__ */
153 	asm volatile ("tlbi vae1is, %0" : : "r"(val));
154 }
155 
156 static inline void
flush_mmu_tlb_entry(uint64_t val)157 flush_mmu_tlb_entry(uint64_t val)
158 {
159 	flush_mmu_tlb_entry_async(val);
160 	sync_tlb_flush();
161 }
162 
163 // flush_mmu_tlb_entries: flush TLB entries that map a VA range and ASID, all cores
164 // start and end must have the ASID in the high 16 bits, with the VA in units of 4K in the lowest bits
165 // Will also flush global entries that match the VA range
166 static inline void
flush_mmu_tlb_entries_async(uint64_t start,uint64_t end,uint64_t pmap_page_size,bool last_level_only)167 flush_mmu_tlb_entries_async(uint64_t start, uint64_t end, uint64_t pmap_page_size, bool last_level_only)
168 {
169 #if __ARM_16K_PG__
170 	if (pmap_page_size == 16384) {
171 		start = start & ~0x3ULL;
172 
173 		/*
174 		 * The code below is not necessarily correct.  From an overview of
175 		 * the client code, the expected contract for TLB flushes is that
176 		 * we will expand from an "address, length" pair to "start address,
177 		 * end address" in the course of a TLB flush.  This suggests that
178 		 * a flush for "X, X+4" is actually only asking for a flush of a
179 		 * single 16KB page.  At the same time, we'd like to be prepared
180 		 * for bad inputs (X, X+3), so add 3 and then truncate the 4KB page
181 		 * number to a 16KB page boundary.  This should deal correctly with
182 		 * unaligned inputs.
183 		 *
184 		 * If our expecations about client behavior are wrong however, this
185 		 * will lead to occasional TLB corruption on platforms with 16KB
186 		 * pages.
187 		 */
188 		end = (end + 0x3ULL) & ~0x3ULL;
189 	}
190 #endif // __ARM_16K_PG__
191 #if __ARM_KERNEL_PROTECT__
192 	uint64_t asid = start >> TLBI_ASID_SHIFT;
193 	/*
194 	 * If we are flushing ASID 0, this is a kernel operation.  With this
195 	 * ASID scheme, this means we should flush all ASIDs.
196 	 */
197 	if (asid == 0) {
198 		if (last_level_only) {
199 			for (; start < end; start += (pmap_page_size / 4096)) {
200 				asm volatile ("tlbi vaale1is, %0" : : "r"(start));
201 			}
202 		} else {
203 			for (; start < end; start += (pmap_page_size / 4096)) {
204 				asm volatile ("tlbi vaae1is, %0" : : "r"(start));
205 			}
206 		}
207 		return;
208 	}
209 	start = start | (1ULL << TLBI_ASID_SHIFT);
210 	end = end | (1ULL << TLBI_ASID_SHIFT);
211 	if (last_level_only) {
212 		for (; start < end; start += (pmap_page_size / 4096)) {
213 			start = start & ~(1ULL << TLBI_ASID_SHIFT);
214 			asm volatile ("tlbi vale1is, %0" : : "r"(start));
215 			start = start | (1ULL << TLBI_ASID_SHIFT);
216 			asm volatile ("tlbi vale1is, %0" : : "r"(start));
217 		}
218 	} else {
219 		for (; start < end; start += (pmap_page_size / 4096)) {
220 			start = start & ~(1ULL << TLBI_ASID_SHIFT);
221 			asm volatile ("tlbi vae1is, %0" : : "r"(start));
222 			start = start | (1ULL << TLBI_ASID_SHIFT);
223 			asm volatile ("tlbi vae1is, %0" : : "r"(start));
224 		}
225 	}
226 #else
227 	if (last_level_only) {
228 		for (; start < end; start += (pmap_page_size / 4096)) {
229 			asm volatile ("tlbi vale1is, %0" : : "r"(start));
230 		}
231 	} else {
232 		for (; start < end; start += (pmap_page_size / 4096)) {
233 			asm volatile ("tlbi vae1is, %0" : : "r"(start));
234 		}
235 	}
236 #endif /* __ARM_KERNEL_PROTECT__ */
237 }
238 
239 static inline void
flush_mmu_tlb_entries(uint64_t start,uint64_t end,uint64_t pmap_page_size,bool last_level_only)240 flush_mmu_tlb_entries(uint64_t start, uint64_t end, uint64_t pmap_page_size, bool last_level_only)
241 {
242 	flush_mmu_tlb_entries_async(start, end, pmap_page_size, last_level_only);
243 	sync_tlb_flush();
244 }
245 
246 // flush_mmu_tlb_asid: flush all entries that match an ASID, on all cores
247 // ASID must be in high 16 bits of argument
248 // Will not flush global entries
249 static inline void
flush_mmu_tlb_asid_async(uint64_t val)250 flush_mmu_tlb_asid_async(uint64_t val)
251 {
252 #if __ARM_KERNEL_PROTECT__
253 	/*
254 	 * If we are flushing ASID 0, this is a kernel operation.  With this
255 	 * ASID scheme, this means we should flush all ASIDs.
256 	 */
257 	uint64_t asid = val >> TLBI_ASID_SHIFT;
258 	if (asid == 0) {
259 		asm volatile ("tlbi vmalle1is");
260 		return;
261 	}
262 	val = val & ~(1ULL << TLBI_ASID_SHIFT);
263 	asm volatile ("tlbi aside1is, %0" : : "r"(val));
264 	val = val | (1ULL << TLBI_ASID_SHIFT);
265 #endif /* __ARM_KERNEL_PROTECT__ */
266 	asm volatile ("tlbi aside1is, %0" : : "r"(val));
267 }
268 
269 static inline void
flush_mmu_tlb_asid(uint64_t val)270 flush_mmu_tlb_asid(uint64_t val)
271 {
272 	flush_mmu_tlb_asid_async(val);
273 	sync_tlb_flush();
274 }
275 
276 // flush_core_tlb_asid: flush all entries that match an ASID, local core only
277 // ASID must be in high 16 bits of argument
278 // Will not flush global entries
279 static inline void
flush_core_tlb_asid_async(uint64_t val)280 flush_core_tlb_asid_async(uint64_t val)
281 {
282 #if __ARM_KERNEL_PROTECT__
283 	/*
284 	 * If we are flushing ASID 0, this is a kernel operation.  With this
285 	 * ASID scheme, this means we should flush all ASIDs.
286 	 */
287 	uint64_t asid = val >> TLBI_ASID_SHIFT;
288 	if (asid == 0) {
289 		asm volatile ("tlbi vmalle1");
290 		return;
291 	}
292 	val = val & ~(1ULL << TLBI_ASID_SHIFT);
293 	asm volatile ("tlbi aside1, %0" : : "r"(val));
294 	val = val | (1ULL << TLBI_ASID_SHIFT);
295 #endif /* __ARM_KERNEL_PROTECT__ */
296 	asm volatile ("tlbi aside1, %0" : : "r"(val));
297 }
298 
299 static inline void
flush_core_tlb_asid(uint64_t val)300 flush_core_tlb_asid(uint64_t val)
301 {
302 	flush_core_tlb_asid_async(val);
303 	sync_tlb_flush_local();
304 }
305 
306 #if __ARM_RANGE_TLBI__
307 #if __ARM_KERNEL_PROTECT__
308 	#error __ARM_RANGE_TLBI__ + __ARM_KERNEL_PROTECT__ is not currently supported
309 #endif
310 
311 #define ARM64_TLB_RANGE_PAGES (1ULL << 21)
312 #define rtlbi_addr(x, shift) (((x) >> (shift)) & RTLBI_ADDR_MASK)
313 #define rtlbi_scale(x) ((uint64_t)(x) << RTLBI_SCALE_SHIFT)
314 #define rtlbi_num(x) ((uint64_t)(x) << RTLBI_NUM_SHIFT)
315 
316 /**
317  * Given the number of pages to invalidate, generate the correct parameter to
318  * pass to any of the TLBI by range methods.
319  */
320 static inline uint64_t
generate_rtlbi_param(ppnum_t npages,uint32_t asid,vm_offset_t va,uint64_t pmap_page_shift)321 generate_rtlbi_param(ppnum_t npages, uint32_t asid, vm_offset_t va, uint64_t pmap_page_shift)
322 {
323 	/**
324 	 * Per the armv8.4 RTLBI extension spec, the range encoded in the rtlbi register operand is defined by:
325 	 * BaseADDR <= VA < BaseADDR+((NUM+1)*2^(5*SCALE+1) * Translation_Granule_Size)
326 	 */
327 	unsigned order = (unsigned)(sizeof(npages) * 8) - (unsigned)__builtin_clz(npages - 1) - 1;
328 	unsigned scale = ((order ? order : 1) - 1) / 5;
329 	unsigned granule = 1 << ((5 * scale) + 1);
330 	unsigned num = (((npages + granule - 1) & ~(granule - 1)) / granule) - 1;
331 	return tlbi_asid(asid) | RTLBI_TG(pmap_page_shift) | rtlbi_scale(scale) | rtlbi_num(num) | rtlbi_addr(va, pmap_page_shift);
332 }
333 
334 // flush_mmu_tlb_range: flush TLB entries that map a VA range using a single instruction
335 // The argument should be encoded according to generate_rtlbi_param().
336 // Follows the same ASID matching behavior as flush_mmu_tlb_entries()
337 static inline void
flush_mmu_tlb_range_async(uint64_t val,bool last_level_only)338 flush_mmu_tlb_range_async(uint64_t val, bool last_level_only)
339 {
340 	if (last_level_only) {
341 		asm volatile ("tlbi rvale1is, %0" : : "r"(val));
342 	} else {
343 		asm volatile ("tlbi rvae1is, %0" : : "r"(val));
344 	}
345 }
346 
347 static inline void
flush_mmu_tlb_range(uint64_t val,bool last_level_only)348 flush_mmu_tlb_range(uint64_t val, bool last_level_only)
349 {
350 	flush_mmu_tlb_range_async(val, last_level_only);
351 	sync_tlb_flush();
352 }
353 
354 // flush_mmu_tlb_allrange: flush TLB entries that map a VA range using a single instruction
355 // The argument should be encoded according to generate_rtlbi_param().
356 // Follows the same ASID matching behavior as flush_mmu_tlb_allentries()
357 static inline void
flush_mmu_tlb_allrange_async(uint64_t val,bool last_level_only)358 flush_mmu_tlb_allrange_async(uint64_t val, bool last_level_only)
359 {
360 	if (last_level_only) {
361 		asm volatile ("tlbi rvaale1is, %0" : : "r"(val));
362 	} else {
363 		asm volatile ("tlbi rvaae1is, %0" : : "r"(val));
364 	}
365 }
366 
367 static inline void
flush_mmu_tlb_allrange(uint64_t val,bool last_level_only)368 flush_mmu_tlb_allrange(uint64_t val, bool last_level_only)
369 {
370 	flush_mmu_tlb_allrange_async(val, last_level_only);
371 	sync_tlb_flush();
372 }
373 
374 // flush_core_tlb_allrange: flush TLB entries that map a VA range using a single instruction, local core only
375 // The argument should be encoded according to generate_rtlbi_param().
376 // Follows the same ASID matching behavior as flush_mmu_tlb_allentries()
377 static inline void
flush_core_tlb_allrange_async(uint64_t val)378 flush_core_tlb_allrange_async(uint64_t val)
379 {
380 	asm volatile ("tlbi rvaae1, %0" : : "r"(val));
381 }
382 
383 static inline void
flush_core_tlb_allrange(uint64_t val)384 flush_core_tlb_allrange(uint64_t val)
385 {
386 	flush_core_tlb_allrange_async(val);
387 	sync_tlb_flush_local();
388 }
389 
390 #endif // __ARM_RANGE_TLBI__
391 
392 
393