xref: /kvm-unit-tests/riscv/sbi-fwft.c (revision 695740795adee59b48599e2f1a6bf19866a77779)
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 
fwft_set_raw(unsigned long feature,unsigned long value,unsigned long flags)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 
fwft_set(uint32_t feature,unsigned long value,unsigned long flags)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 
fwft_get_raw(unsigned long feature)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 
fwft_get(uint32_t feature)38 static struct sbiret fwft_get(uint32_t feature)
39 {
40 	return fwft_get_raw(feature);
41 }
42 
fwft_set_and_check_raw(const char * str,unsigned long feature,unsigned long value,unsigned long flags)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 
fwft_check_reserved(unsigned long id)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 */
fwft_check_reset(uint32_t feature,unsigned long reset)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 */
fwft_feature_lock_test_values(uint32_t feature,size_t nr_values,unsigned long test_values[],unsigned long locked_value)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 	bool kfail = __sbi_get_imp_id() == SBI_IMPL_OPENSBI &&
87 		     __sbi_get_imp_version() < sbi_impl_opensbi_mk_version(1, 7);
88 
89 	for (int i = 0; i < nr_values; ++i) {
90 		ret = fwft_set(feature, test_values[i], 0);
91 		sbiret_kfail_error(kfail, &ret, SBI_ERR_DENIED_LOCKED,
92 				   "Set to %lu without lock flag", test_values[i]);
93 
94 		ret = fwft_set(feature, test_values[i], SBI_FWFT_SET_FLAG_LOCK);
95 		sbiret_kfail_error(kfail, &ret, SBI_ERR_DENIED_LOCKED,
96 				   "Set to %lu with lock flag", test_values[i]);
97 	}
98 
99 	ret = fwft_get(feature);
100 	sbiret_report(&ret, SBI_SUCCESS, locked_value, "Get value %lu", locked_value);
101 
102 	report_prefix_pop();
103 }
104 
fwft_feature_lock_test(uint32_t feature,unsigned long locked_value)105 static void fwft_feature_lock_test(uint32_t feature, unsigned long locked_value)
106 {
107 	unsigned long values[] = {0, 1};
108 
109 	fwft_feature_lock_test_values(feature, 2, values, locked_value);
110 }
111 
fwft_check_base(void)112 static void fwft_check_base(void)
113 {
114 	report_prefix_push("base");
115 
116 	fwft_check_reserved(SBI_FWFT_LOCAL_RESERVED_START);
117 	fwft_check_reserved(SBI_FWFT_LOCAL_RESERVED_END);
118 	fwft_check_reserved(SBI_FWFT_GLOBAL_RESERVED_START);
119 	fwft_check_reserved(SBI_FWFT_GLOBAL_RESERVED_END);
120 
121 	report_prefix_pop();
122 }
123 
124 static bool misaligned_handled;
125 
misaligned_handler(struct pt_regs * regs)126 static void misaligned_handler(struct pt_regs *regs)
127 {
128 	misaligned_handled = true;
129 	regs->epc += 4;
130 }
131 
fwft_misaligned_exc_set(unsigned long value,unsigned long flags)132 static struct sbiret fwft_misaligned_exc_set(unsigned long value, unsigned long flags)
133 {
134 	return fwft_set(SBI_FWFT_MISALIGNED_EXC_DELEG, value, flags);
135 }
136 
fwft_misaligned_exc_get(void)137 static struct sbiret fwft_misaligned_exc_get(void)
138 {
139 	return fwft_get(SBI_FWFT_MISALIGNED_EXC_DELEG);
140 }
141 
fwft_check_misaligned_exc_deleg(void)142 static void fwft_check_misaligned_exc_deleg(void)
143 {
144 	struct sbiret ret;
145 	unsigned long expected;
146 
147 	report_prefix_push("misaligned_exc_deleg");
148 
149 	ret = fwft_misaligned_exc_get();
150 	if (ret.error != SBI_SUCCESS) {
151 		if (env_enabled("SBI_HAVE_FWFT_MISALIGNED_EXC_DELEG")) {
152 			sbiret_report_error(&ret, SBI_SUCCESS, "supported");
153 			return;
154 		}
155 		report_skip("not supported by platform");
156 		return;
157 	}
158 
159 	if (!sbiret_report_error(&ret, SBI_SUCCESS, "Get misaligned deleg feature"))
160 		return;
161 
162 	if (env_or_skip("MISALIGNED_EXC_DELEG_RESET")) {
163 		expected = strtoul(getenv("MISALIGNED_EXC_DELEG_RESET"), NULL, 0);
164 		fwft_check_reset(SBI_FWFT_MISALIGNED_EXC_DELEG, expected);
165 	}
166 
167 	ret = fwft_misaligned_exc_set(2, 0);
168 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
169 			    "Set misaligned deleg feature invalid value 2");
170 	ret = fwft_misaligned_exc_set(0xFFFFFFFF, 0);
171 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
172 			    "Set misaligned deleg feature invalid value 0xFFFFFFFF");
173 
174 #if __riscv_xlen > 32
175 	ret = fwft_misaligned_exc_set(BIT(32), 0);
176 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
177 			    "Set misaligned deleg with invalid value > 32bits");
178 
179 	ret = fwft_misaligned_exc_set(0, BIT(32));
180 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM,
181 			    "Set misaligned deleg with invalid flag > 32bits");
182 #endif
183 
184 	/* Set to 0 and check after with get */
185 	fwft_set_and_check_raw("", SBI_FWFT_MISALIGNED_EXC_DELEG, 0, 0);
186 
187 	/* Set to 1 and check after with get */
188 	fwft_set_and_check_raw("", SBI_FWFT_MISALIGNED_EXC_DELEG, 1, 0);
189 
190 	install_exception_handler(EXC_LOAD_MISALIGNED, misaligned_handler);
191 
192 	asm volatile (
193 		".option push\n"
194 		/*
195 		 * Disable compression so the lw takes exactly 4 bytes and thus
196 		 * can be skipped reliably from the exception handler.
197 		 */
198 		".option arch,-c\n"
199 		"lw %[val], 1(%[val_addr])\n"
200 		".option pop\n"
201 		: [val] "+r" (ret.value)
202 		: [val_addr] "r" (&ret.value)
203 		: "memory");
204 
205 	/*
206 	 * Even though the SBI delegated the misaligned exception to S-mode, it might not trap on
207 	 * misaligned load/store access, report that during tests.
208 	 */
209 	if (!misaligned_handled)
210 		report_skip("Misaligned load exception does not trap in S-mode");
211 	else
212 		report_pass("Misaligned load exception trap in S-mode");
213 
214 	install_exception_handler(EXC_LOAD_MISALIGNED, NULL);
215 
216 	/* Lock the feature */
217 	ret = fwft_misaligned_exc_set(0, SBI_FWFT_SET_FLAG_LOCK);
218 	sbiret_report_error(&ret, SBI_SUCCESS, "Set misaligned deleg feature value 0 and lock");
219 
220 	fwft_feature_lock_test(SBI_FWFT_MISALIGNED_EXC_DELEG, 0);
221 
222 	report_prefix_pop();
223 }
224 
225 static bool adue_triggered_read, adue_triggered_write;
226 
adue_set_ad(unsigned long addr,pteval_t prot)227 static void adue_set_ad(unsigned long addr, pteval_t prot)
228 {
229 	pte_t *ptep = get_pte(current_pgtable(), addr);
230 	*ptep = __pte(pte_val(*ptep) | prot);
231 	local_flush_tlb_page(addr);
232 }
233 
adue_read_handler(struct pt_regs * regs)234 static void adue_read_handler(struct pt_regs *regs)
235 {
236 	adue_triggered_read = true;
237 	adue_set_ad(regs->badaddr, _PAGE_ACCESSED);
238 }
239 
adue_write_handler(struct pt_regs * regs)240 static void adue_write_handler(struct pt_regs *regs)
241 {
242 	adue_triggered_write = true;
243 	adue_set_ad(regs->badaddr, _PAGE_ACCESSED | _PAGE_DIRTY);
244 }
245 
adue_check_pte(pteval_t pte,bool write)246 static bool adue_check_pte(pteval_t pte, bool write)
247 {
248 	return (pte & (_PAGE_ACCESSED | _PAGE_DIRTY)) == (_PAGE_ACCESSED | (write ? _PAGE_DIRTY : 0));
249 }
250 
adue_check(bool hw_updating_enabled,bool write)251 static void adue_check(bool hw_updating_enabled, bool write)
252 {
253 	unsigned long *ptr = malloc(sizeof(unsigned long));
254 	pte_t *ptep = get_pte(current_pgtable(), (uintptr_t)ptr);
255 	bool *triggered;
256 	const char *op;
257 
258 	WRITE_ONCE(adue_triggered_read, false);
259 	WRITE_ONCE(adue_triggered_write, false);
260 
261 	*ptep = __pte(pte_val(*ptep) & ~(_PAGE_ACCESSED | _PAGE_DIRTY));
262 	local_flush_tlb_page((uintptr_t)ptr);
263 
264 	if (write) {
265 		op = "write";
266 		triggered = &adue_triggered_write;
267 		writel(0xdeadbeef, ptr);
268 	} else {
269 		op = "read";
270 		triggered = &adue_triggered_read;
271 		readl(ptr);
272 	}
273 
274 	report(hw_updating_enabled != *triggered &&
275 	       adue_check_pte(pte_val(*ptep), write), "hw updating %s %s",
276 	       hw_updating_enabled ? "enabled" : "disabled", op);
277 
278 	free(ptr);
279 }
280 
adue_toggle_and_check_raw(const char * str,unsigned long feature,unsigned long value,unsigned long flags)281 static bool adue_toggle_and_check_raw(const char *str, unsigned long feature, unsigned long value,
282 				      unsigned long flags)
283 {
284 	struct sbiret ret = fwft_set_and_check_raw(str, feature, value, flags);
285 
286 	if (!ret.error) {
287 		adue_check(value, false);
288 		adue_check(value, true);
289 		return true;
290 	}
291 
292 	return false;
293 }
294 
adue_toggle_and_check(const char * str,unsigned long value,unsigned long flags)295 static bool adue_toggle_and_check(const char *str, unsigned long value, unsigned long flags)
296 {
297 	return adue_toggle_and_check_raw(str, SBI_FWFT_PTE_AD_HW_UPDATING, value, flags);
298 }
299 
fwft_check_pte_ad_hw_updating(void)300 static void fwft_check_pte_ad_hw_updating(void)
301 {
302 	struct sbiret ret;
303 	bool enabled;
304 
305 	report_prefix_push("pte_ad_hw_updating");
306 
307 	ret = fwft_get(SBI_FWFT_PTE_AD_HW_UPDATING);
308 	if (ret.error != SBI_SUCCESS) {
309 		if (env_enabled("SBI_HAVE_FWFT_PTE_AD_HW_UPDATING")) {
310 			sbiret_report_error(&ret, SBI_SUCCESS, "supported");
311 			return;
312 		}
313 		report_skip("not supported by platform");
314 		return;
315 	} else if (!sbiret_report_error(&ret, SBI_SUCCESS, "get")) {
316 		/* Not much we can do without a working get... */
317 		return;
318 	}
319 
320 	report(ret.value == 0 || ret.value == 1, "first get value is 0/1");
321 
322 	enabled = ret.value;
323 
324 	bool kfail = __sbi_get_imp_id() == SBI_IMPL_OPENSBI &&
325 		     __sbi_get_imp_version() < sbi_impl_opensbi_mk_version(1, 7);
326 	report_kfail(kfail, !enabled, "resets to 0");
327 
328 	install_exception_handler(EXC_LOAD_PAGE_FAULT, adue_read_handler);
329 	install_exception_handler(EXC_STORE_PAGE_FAULT, adue_write_handler);
330 
331 	adue_check(enabled, false);
332 	adue_check(enabled, true);
333 
334 	if (!adue_toggle_and_check("", !enabled, 0))
335 		goto adue_inval_tests;
336 	else
337 		enabled = !enabled;
338 
339 	if (!adue_toggle_and_check(" again", !enabled, 0))
340 		goto adue_inval_tests;
341 	else
342 		enabled = !enabled;
343 
344 #if __riscv_xlen > 32
345 	if (!adue_toggle_and_check_raw(" with high feature bits set",
346 				       BIT(32) | SBI_FWFT_PTE_AD_HW_UPDATING, !enabled, 0))
347 		goto adue_inval_tests;
348 	else
349 		enabled = !enabled;
350 #endif
351 
352 adue_inval_tests:
353 	ret = fwft_set(SBI_FWFT_PTE_AD_HW_UPDATING, 2, 0);
354 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM, "set to 2");
355 
356 	ret = fwft_set(SBI_FWFT_PTE_AD_HW_UPDATING, !enabled, 2);
357 	sbiret_report_error(&ret, SBI_ERR_INVALID_PARAM, "set to %d with flags=2", !enabled);
358 
359 	if (!adue_toggle_and_check(" with lock", !enabled, 1))
360 		goto adue_done;
361 	else
362 		enabled = !enabled;
363 
364 	fwft_feature_lock_test(SBI_FWFT_PTE_AD_HW_UPDATING, enabled);
365 
366 adue_done:
367 	install_exception_handler(EXC_LOAD_PAGE_FAULT, NULL);
368 	install_exception_handler(EXC_STORE_PAGE_FAULT, NULL);
369 
370 	report_prefix_pop();
371 }
372 
check_fwft(void)373 void check_fwft(void)
374 {
375 	report_prefix_push("fwft");
376 
377 	if (!sbi_probe(SBI_EXT_FWFT)) {
378 		report_skip("FWFT extension not available");
379 		report_prefix_pop();
380 		return;
381 	}
382 
383 	sbi_bad_fid(SBI_EXT_FWFT);
384 
385 	fwft_check_base();
386 	fwft_check_misaligned_exc_deleg();
387 	fwft_check_pte_ad_hw_updating();
388 
389 	report_prefix_pop();
390 }
391