xref: /kvm-unit-tests/riscv/sbi-fwft.c (revision a3fc8778e92d12896807b35e781bf21b7dc64db3)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * SBI verification
4  *
5  * Copyright (C) 2024, Rivos Inc., Clément Léger <cleger@rivosinc.com>
6  */
7 #include <libcflat.h>
8 #include <alloc.h>
9 #include <stdlib.h>
10 
11 #include <asm/csr.h>
12 #include <asm/io.h>
13 #include <asm/mmu.h>
14 #include <asm/processor.h>
15 #include <asm/ptrace.h>
16 #include <asm/sbi.h>
17 
18 #include "sbi-tests.h"
19 
20 void check_fwft(void);
21 
22 
23 static struct sbiret fwft_set_raw(unsigned long feature, unsigned long value, unsigned long flags)
24 {
25 	return sbi_ecall(SBI_EXT_FWFT, SBI_EXT_FWFT_SET, feature, value, flags, 0, 0, 0);
26 }
27 
28 static struct sbiret fwft_set(uint32_t feature, unsigned long value, unsigned long flags)
29 {
30 	return fwft_set_raw(feature, value, flags);
31 }
32 
33 static struct sbiret fwft_get_raw(unsigned long feature)
34 {
35 	return sbi_ecall(SBI_EXT_FWFT, SBI_EXT_FWFT_GET, feature, 0, 0, 0, 0, 0);
36 }
37 
38 static struct sbiret fwft_get(uint32_t feature)
39 {
40 	return fwft_get_raw(feature);
41 }
42 
43 static struct sbiret fwft_set_and_check_raw(const char *str, unsigned long feature,
44 					    unsigned long value, unsigned long flags)
45 {
46 	struct sbiret ret;
47 
48 	ret = fwft_set_raw(feature, value, flags);
49 	if (!sbiret_report_error(&ret, SBI_SUCCESS, "set to %ld%s", value, str))
50 		return ret;
51 
52 	ret = fwft_get_raw(feature);
53 	sbiret_report(&ret, SBI_SUCCESS, value, "get %ld after set%s", value, str);
54 
55 	return ret;
56 }
57 
58 static void fwft_check_reserved(unsigned long id)
59 {
60 	struct sbiret ret;
61 
62 	ret = fwft_get(id);
63 	sbiret_report_error(&ret, SBI_ERR_DENIED, "get reserved feature 0x%lx", id);
64 
65 	ret = fwft_set(id, 1, 0);
66 	sbiret_report_error(&ret, SBI_ERR_DENIED, "set reserved feature 0x%lx", id);
67 }
68 
69 /* Must be called before any fwft_set() call is made for @feature */
70 static void fwft_check_reset(uint32_t feature, unsigned long reset)
71 {
72 	struct sbiret ret = fwft_get(feature);
73 
74 	sbiret_report(&ret, SBI_SUCCESS, reset, "resets to %lu", reset);
75 }
76 
77 /* Must be called after locking the feature using SBI_FWFT_SET_FLAG_LOCK */
78 static void fwft_feature_lock_test_values(uint32_t feature, size_t nr_values,
79 					  unsigned long test_values[],
80 					  unsigned long locked_value)
81 {
82 	struct sbiret ret;
83 
84 	report_prefix_push("locked");
85 
86 	for (int i = 0; i < nr_values; ++i) {
87 		ret = fwft_set(feature, test_values[i], 0);
88 		sbiret_report_error(&ret, SBI_ERR_DENIED_LOCKED,
89 				    "Set to %lu without lock flag", test_values[i]);
90 
91 		ret = fwft_set(feature, test_values[i], SBI_FWFT_SET_FLAG_LOCK);
92 		sbiret_report_error(&ret, SBI_ERR_DENIED_LOCKED,
93 				    "Set to %lu with lock flag", test_values[i]);
94 	}
95 
96 	ret = fwft_get(feature);
97 	sbiret_report(&ret, SBI_SUCCESS, locked_value, "Get value %lu", locked_value);
98 
99 	report_prefix_pop();
100 }
101 
102 static void fwft_feature_lock_test(uint32_t feature, unsigned long locked_value)
103 {
104 	unsigned long values[] = {0, 1};
105 
106 	fwft_feature_lock_test_values(feature, 2, values, locked_value);
107 }
108 
109 static void fwft_check_base(void)
110 {
111 	report_prefix_push("base");
112 
113 	fwft_check_reserved(SBI_FWFT_LOCAL_RESERVED_START);
114 	fwft_check_reserved(SBI_FWFT_LOCAL_RESERVED_END);
115 	fwft_check_reserved(SBI_FWFT_GLOBAL_RESERVED_START);
116 	fwft_check_reserved(SBI_FWFT_GLOBAL_RESERVED_END);
117 
118 	report_prefix_pop();
119 }
120 
121 static bool misaligned_handled;
122 
123 static void misaligned_handler(struct pt_regs *regs)
124 {
125 	misaligned_handled = true;
126 	regs->epc += 4;
127 }
128 
129 static struct sbiret fwft_misaligned_exc_set(unsigned long value, unsigned long flags)
130 {
131 	return fwft_set(SBI_FWFT_MISALIGNED_EXC_DELEG, value, flags);
132 }
133 
134 static struct sbiret fwft_misaligned_exc_get(void)
135 {
136 	return fwft_get(SBI_FWFT_MISALIGNED_EXC_DELEG);
137 }
138 
139 static void fwft_check_misaligned_exc_deleg(void)
140 {
141 	struct sbiret ret;
142 	unsigned long expected;
143 
144 	report_prefix_push("misaligned_exc_deleg");
145 
146 	ret = fwft_misaligned_exc_get();
147 	if (ret.error != SBI_SUCCESS) {
148 		if (env_enabled("SBI_HAVE_FWFT_MISALIGNED_EXC_DELEG")) {
149 			sbiret_report_error(&ret, SBI_SUCCESS, "supported");
150 			return;
151 		}
152 		report_skip("not supported by platform");
153 		return;
154 	}
155 
156 	if (!sbiret_report_error(&ret, SBI_SUCCESS, "Get misaligned deleg feature"))
157 		return;
158 
159 	if (env_or_skip("MISALIGNED_EXC_DELEG_RESET")) {
160 		expected = strtoul(getenv("MISALIGNED_EXC_DELEG_RESET"), NULL, 0);
161 		fwft_check_reset(SBI_FWFT_MISALIGNED_EXC_DELEG, expected);
162 	}
163 
164 	ret = fwft_misaligned_exc_set(2, 0);
165 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
166 			    "Set misaligned deleg feature invalid value 2");
167 	ret = fwft_misaligned_exc_set(0xFFFFFFFF, 0);
168 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
169 			    "Set misaligned deleg feature invalid value 0xFFFFFFFF");
170 
171 #if __riscv_xlen > 32
172 	ret = fwft_misaligned_exc_set(BIT(32), 0);
173 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
174 			    "Set misaligned deleg with invalid value > 32bits");
175 
176 	ret = fwft_misaligned_exc_set(0, BIT(32));
177 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
178 			    "Set misaligned deleg with invalid flag > 32bits");
179 #endif
180 
181 	/* Set to 0 and check after with get */
182 	fwft_set_and_check_raw("", SBI_FWFT_MISALIGNED_EXC_DELEG, 0, 0);
183 
184 	/* Set to 1 and check after with get */
185 	fwft_set_and_check_raw("", SBI_FWFT_MISALIGNED_EXC_DELEG, 1, 0);
186 
187 	install_exception_handler(EXC_LOAD_MISALIGNED, misaligned_handler);
188 
189 	asm volatile (
190 		".option push\n"
191 		/*
192 		 * Disable compression so the lw takes exactly 4 bytes and thus
193 		 * can be skipped reliably from the exception handler.
194 		 */
195 		".option arch,-c\n"
196 		"lw %[val], 1(%[val_addr])\n"
197 		".option pop\n"
198 		: [val] "+r" (ret.value)
199 		: [val_addr] "r" (&ret.value)
200 		: "memory");
201 
202 	/*
203 	 * Even though the SBI delegated the misaligned exception to S-mode, it might not trap on
204 	 * misaligned load/store access, report that during tests.
205 	 */
206 	if (!misaligned_handled)
207 		report_skip("Misaligned load exception does not trap in S-mode");
208 	else
209 		report_pass("Misaligned load exception trap in S-mode");
210 
211 	install_exception_handler(EXC_LOAD_MISALIGNED, NULL);
212 
213 	/* Lock the feature */
214 	ret = fwft_misaligned_exc_set(0, SBI_FWFT_SET_FLAG_LOCK);
215 	sbiret_report_error(&ret, SBI_SUCCESS, "Set misaligned deleg feature value 0 and lock");
216 
217 	fwft_feature_lock_test(SBI_FWFT_MISALIGNED_EXC_DELEG, 0);
218 
219 	report_prefix_pop();
220 }
221 
222 static bool adue_triggered_read, adue_triggered_write;
223 
224 static void adue_set_ad(unsigned long addr, pteval_t prot)
225 {
226 	pte_t *ptep = get_pte(current_pgtable(), addr);
227 	*ptep = __pte(pte_val(*ptep) | prot);
228 	local_flush_tlb_page(addr);
229 }
230 
231 static void adue_read_handler(struct pt_regs *regs)
232 {
233 	adue_triggered_read = true;
234 	adue_set_ad(regs->badaddr, _PAGE_ACCESSED);
235 }
236 
237 static void adue_write_handler(struct pt_regs *regs)
238 {
239 	adue_triggered_write = true;
240 	adue_set_ad(regs->badaddr, _PAGE_ACCESSED | _PAGE_DIRTY);
241 }
242 
243 static bool adue_check_pte(pteval_t pte, bool write)
244 {
245 	return (pte & (_PAGE_ACCESSED | _PAGE_DIRTY)) == (_PAGE_ACCESSED | (write ? _PAGE_DIRTY : 0));
246 }
247 
248 static void adue_check(bool hw_updating_enabled, bool write)
249 {
250 	unsigned long *ptr = malloc(sizeof(unsigned long));
251 	pte_t *ptep = get_pte(current_pgtable(), (uintptr_t)ptr);
252 	bool *triggered;
253 	const char *op;
254 
255 	WRITE_ONCE(adue_triggered_read, false);
256 	WRITE_ONCE(adue_triggered_write, false);
257 
258 	*ptep = __pte(pte_val(*ptep) & ~(_PAGE_ACCESSED | _PAGE_DIRTY));
259 	local_flush_tlb_page((uintptr_t)ptr);
260 
261 	if (write) {
262 		op = "write";
263 		triggered = &adue_triggered_write;
264 		writel(0xdeadbeef, ptr);
265 	} else {
266 		op = "read";
267 		triggered = &adue_triggered_read;
268 		readl(ptr);
269 	}
270 
271 	report(hw_updating_enabled != *triggered &&
272 	       adue_check_pte(pte_val(*ptep), write), "hw updating %s %s",
273 	       hw_updating_enabled ? "enabled" : "disabled", op);
274 
275 	free(ptr);
276 }
277 
278 static bool adue_toggle_and_check_raw(const char *str, unsigned long feature, unsigned long value,
279 				      unsigned long flags)
280 {
281 	struct sbiret ret = fwft_set_and_check_raw(str, feature, value, flags);
282 
283 	if (!ret.error) {
284 		adue_check(value, false);
285 		adue_check(value, true);
286 		return true;
287 	}
288 
289 	return false;
290 }
291 
292 static bool adue_toggle_and_check(const char *str, unsigned long value, unsigned long flags)
293 {
294 	return adue_toggle_and_check_raw(str, SBI_FWFT_PTE_AD_HW_UPDATING, value, flags);
295 }
296 
297 static void fwft_check_pte_ad_hw_updating(void)
298 {
299 	struct sbiret ret;
300 	bool enabled;
301 
302 	report_prefix_push("pte_ad_hw_updating");
303 
304 	ret = fwft_get(SBI_FWFT_PTE_AD_HW_UPDATING);
305 	if (ret.error != SBI_SUCCESS) {
306 		if (env_enabled("SBI_HAVE_FWFT_PTE_AD_HW_UPDATING")) {
307 			sbiret_report_error(&ret, SBI_SUCCESS, "supported");
308 			return;
309 		}
310 		report_skip("not supported by platform");
311 		return;
312 	} else if (!sbiret_report_error(&ret, SBI_SUCCESS, "get")) {
313 		/* Not much we can do without a working get... */
314 		return;
315 	}
316 
317 	report(ret.value == 0 || ret.value == 1, "first get value is 0/1");
318 
319 	enabled = ret.value;
320 	report_kfail(true, !enabled, "resets to 0");
321 
322 	install_exception_handler(EXC_LOAD_PAGE_FAULT, adue_read_handler);
323 	install_exception_handler(EXC_STORE_PAGE_FAULT, adue_write_handler);
324 
325 	adue_check(enabled, false);
326 	adue_check(enabled, true);
327 
328 	if (!adue_toggle_and_check("", !enabled, 0))
329 		goto adue_inval_tests;
330 	else
331 		enabled = !enabled;
332 
333 	if (!adue_toggle_and_check(" again", !enabled, 0))
334 		goto adue_inval_tests;
335 	else
336 		enabled = !enabled;
337 
338 #if __riscv_xlen > 32
339 	if (!adue_toggle_and_check_raw(" with high feature bits set",
340 				       BIT(32) | SBI_FWFT_PTE_AD_HW_UPDATING, !enabled, 0))
341 		goto adue_inval_tests;
342 	else
343 		enabled = !enabled;
344 #endif
345 
346 adue_inval_tests:
347 	ret = fwft_set(SBI_FWFT_PTE_AD_HW_UPDATING, 2, 0);
348 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM, "set to 2");
349 
350 	ret = fwft_set(SBI_FWFT_PTE_AD_HW_UPDATING, !enabled, 2);
351 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM, "set to %d with flags=2", !enabled);
352 
353 	if (!adue_toggle_and_check(" with lock", !enabled, 1))
354 		goto adue_done;
355 	else
356 		enabled = !enabled;
357 
358 	fwft_feature_lock_test(SBI_FWFT_PTE_AD_HW_UPDATING, enabled);
359 
360 adue_done:
361 	install_exception_handler(EXC_LOAD_PAGE_FAULT, NULL);
362 	install_exception_handler(EXC_STORE_PAGE_FAULT, NULL);
363 
364 	report_prefix_pop();
365 }
366 
367 void check_fwft(void)
368 {
369 	report_prefix_push("fwft");
370 
371 	if (!sbi_probe(SBI_EXT_FWFT)) {
372 		report_skip("FWFT extension not available");
373 		report_prefix_pop();
374 		return;
375 	}
376 
377 	sbi_bad_fid(SBI_EXT_FWFT);
378 
379 	fwft_check_base();
380 	fwft_check_misaligned_exc_deleg();
381 	fwft_check_pte_ad_hw_updating();
382 
383 	report_prefix_pop();
384 }
385