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