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