1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2015-2016 Ruslan Bukin <br@bsdpad.com>
5 * All rights reserved.
6 * Copyright (c) 2022 Mitchell Horne <mhorne@FreeBSD.org>
7 * Copyright (c) 2023 The FreeBSD Foundation
8 *
9 * Portions of this software were developed by SRI International and the
10 * University of Cambridge Computer Laboratory under DARPA/AFRL contract
11 * FA8750-10-C-0237 ("CTSRD"), as part of the DARPA CRASH research programme.
12 *
13 * Portions of this software were developed by the University of Cambridge
14 * Computer Laboratory as part of the CTSRD Project, with support from the
15 * UK Higher Education Innovation Fund (HEIF).
16 *
17 * Portions of this software were developed by Mitchell Horne
18 * <mhorne@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
19 *
20 * Redistribution and use in source and binary forms, with or without
21 * modification, are permitted provided that the following conditions
22 * are met:
23 * 1. Redistributions of source code must retain the above copyright
24 * notice, this list of conditions and the following disclaimer.
25 * 2. Redistributions in binary form must reproduce the above copyright
26 * notice, this list of conditions and the following disclaimer in the
27 * documentation and/or other materials provided with the distribution.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 * SUCH DAMAGE.
40 */
41
42 #include "opt_platform.h"
43
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/ctype.h>
47 #include <sys/kernel.h>
48 #include <sys/pcpu.h>
49 #include <sys/sysctl.h>
50
51 #include <machine/cpu.h>
52 #include <machine/cpufunc.h>
53 #include <machine/elf.h>
54 #include <machine/md_var.h>
55
56 #ifdef FDT
57 #include <dev/fdt/fdt_common.h>
58 #include <dev/ofw/openfirm.h>
59 #include <dev/ofw/ofw_bus_subr.h>
60 #endif
61
62 char machine[] = "riscv";
63
64 SYSCTL_STRING(_hw, HW_MACHINE, machine, CTLFLAG_RD | CTLFLAG_CAPRD, machine, 0,
65 "Machine class");
66
67 /* Hardware implementation info. These values may be empty. */
68 register_t mvendorid; /* The CPU's JEDEC vendor ID */
69 register_t marchid; /* The architecture ID */
70 register_t mimpid; /* The implementation ID */
71
72 u_int mmu_caps;
73
74 /* Supervisor-mode extension support. */
75 bool __read_frequently has_sstc;
76 bool __read_frequently has_sscofpmf;
77
78 struct cpu_desc {
79 const char *cpu_mvendor_name;
80 const char *cpu_march_name;
81 u_int isa_extensions; /* Single-letter extensions. */
82 u_int mmu_caps;
83 u_int smode_extensions;
84 #define SV_SSTC (1 << 0)
85 #define SV_SVNAPOT (1 << 1)
86 #define SV_SVPBMT (1 << 2)
87 #define SV_SVINVAL (1 << 3)
88 #define SV_SSCOFPMF (1 << 4)
89 };
90
91 struct cpu_desc cpu_desc[MAXCPU];
92
93 /*
94 * Micro-architecture tables.
95 */
96 struct marchid_entry {
97 register_t march_id;
98 const char *march_name;
99 };
100
101 #define MARCHID_END { -1ul, NULL }
102
103 /* Open-source RISC-V architecture IDs; globally allocated. */
104 static const struct marchid_entry global_marchids[] = {
105 { MARCHID_UCB_ROCKET, "UC Berkeley Rocket" },
106 { MARCHID_UCB_BOOM, "UC Berkeley Boom" },
107 { MARCHID_UCB_SPIKE, "UC Berkeley Spike" },
108 { MARCHID_UCAM_RVBS, "University of Cambridge RVBS" },
109 MARCHID_END
110 };
111
112 static const struct marchid_entry sifive_marchids[] = {
113 { MARCHID_SIFIVE_U7, "6/7/P200/X200-Series Processor" },
114 MARCHID_END
115 };
116
117 /*
118 * Known CPU vendor/manufacturer table.
119 */
120 static const struct {
121 register_t mvendor_id;
122 const char *mvendor_name;
123 const struct marchid_entry *marchid_table;
124 } mvendor_ids[] = {
125 { MVENDORID_UNIMPL, "Unspecified", NULL },
126 { MVENDORID_SIFIVE, "SiFive", sifive_marchids },
127 { MVENDORID_THEAD, "T-Head", NULL },
128 };
129
130 /*
131 * The ISA string describes the complete set of instructions supported by a
132 * RISC-V CPU. The string begins with a small prefix (e.g. rv64) indicating the
133 * base ISA. It is followed first by single-letter ISA extensions, and then
134 * multi-letter ISA extensions.
135 *
136 * Underscores are used mainly to separate consecutive multi-letter extensions,
137 * but may optionally appear between any two extensions. An extension may be
138 * followed by a version number, in the form of 'Mpm', where M is the
139 * extension's major version number, and 'm' is the minor version number.
140 *
141 * The format is described in detail by the "ISA Extension Naming Conventions"
142 * chapter of the unprivileged spec.
143 */
144 #define ISA_PREFIX ("rv" __XSTRING(__riscv_xlen))
145 #define ISA_PREFIX_LEN (sizeof(ISA_PREFIX) - 1)
146
147 static __inline int
parse_ext_s(struct cpu_desc * desc,char * isa,int idx,int len)148 parse_ext_s(struct cpu_desc *desc, char *isa, int idx, int len)
149 {
150 #define CHECK_S_EXT(str, flag) \
151 do { \
152 if (strncmp(&isa[idx], (str), \
153 MIN(strlen(str), len - idx)) == 0) { \
154 desc->smode_extensions |= flag; \
155 return (idx + strlen(str)); \
156 } \
157 } while (0)
158
159 /* Check for known/supported extensions. */
160 CHECK_S_EXT("sstc", SV_SSTC);
161 CHECK_S_EXT("svnapot", SV_SVNAPOT);
162 CHECK_S_EXT("svpbmt", SV_SVPBMT);
163 CHECK_S_EXT("svinval", SV_SVINVAL);
164 CHECK_S_EXT("sscofpmf", SV_SSCOFPMF);
165
166 #undef CHECK_S_EXT
167
168 /*
169 * Proceed to the next multi-letter extension or the end of the
170 * string.
171 */
172 while (isa[idx] != '_' && idx < len) {
173 idx++;
174 }
175
176 return (idx);
177 }
178
179 static __inline int
parse_ext_x(struct cpu_desc * desc __unused,char * isa,int idx,int len)180 parse_ext_x(struct cpu_desc *desc __unused, char *isa, int idx, int len)
181 {
182 /*
183 * Proceed to the next multi-letter extension or the end of the
184 * string.
185 */
186 while (isa[idx] != '_' && idx < len) {
187 idx++;
188 }
189
190 return (idx);
191 }
192
193 static __inline int
parse_ext_z(struct cpu_desc * desc __unused,char * isa,int idx,int len)194 parse_ext_z(struct cpu_desc *desc __unused, char *isa, int idx, int len)
195 {
196 /*
197 * Proceed to the next multi-letter extension or the end of the
198 * string.
199 *
200 * TODO: parse some of these.
201 */
202 while (isa[idx] != '_' && idx < len) {
203 idx++;
204 }
205
206 return (idx);
207 }
208
209 static __inline int
parse_ext_version(char * isa,int idx,u_int * majorp __unused,u_int * minorp __unused)210 parse_ext_version(char *isa, int idx, u_int *majorp __unused,
211 u_int *minorp __unused)
212 {
213 /* Major version. */
214 while (isdigit(isa[idx]))
215 idx++;
216
217 if (isa[idx] != 'p')
218 return (idx);
219 else
220 idx++;
221
222 /* Minor version. */
223 while (isdigit(isa[idx]))
224 idx++;
225
226 return (idx);
227 }
228
229 /*
230 * Parse the ISA string, building up the set of HWCAP bits as they are found.
231 */
232 static int
parse_riscv_isa(struct cpu_desc * desc,char * isa,int len)233 parse_riscv_isa(struct cpu_desc *desc, char *isa, int len)
234 {
235 int i;
236
237 /* Check the string prefix. */
238 if (strncmp(isa, ISA_PREFIX, ISA_PREFIX_LEN) != 0) {
239 printf("%s: Unrecognized ISA string: %s\n", __func__, isa);
240 return (-1);
241 }
242
243 i = ISA_PREFIX_LEN;
244 while (i < len) {
245 switch(isa[i]) {
246 case 'a':
247 case 'c':
248 case 'd':
249 case 'f':
250 case 'i':
251 case 'm':
252 desc->isa_extensions |= HWCAP_ISA_BIT(isa[i]);
253 i++;
254 break;
255 case 'g':
256 desc->isa_extensions |= HWCAP_ISA_G;
257 i++;
258 break;
259 case 's':
260 /*
261 * XXX: older versions of this string erroneously
262 * indicated supervisor and user mode support as
263 * single-letter extensions. Detect and skip both 's'
264 * and 'u'.
265 */
266 if (isa[i - 1] != '_' && isa[i + 1] == 'u') {
267 i += 2;
268 continue;
269 }
270
271 /*
272 * Supervisor-level extension namespace.
273 */
274 i = parse_ext_s(desc, isa, i, len);
275 break;
276 case 'x':
277 /*
278 * Custom extension namespace. For now, we ignore
279 * these.
280 */
281 i = parse_ext_x(desc, isa, i, len);
282 break;
283 case 'z':
284 /*
285 * Multi-letter standard extension namespace.
286 */
287 i = parse_ext_z(desc, isa, i, len);
288 break;
289 case '_':
290 i++;
291 continue;
292 default:
293 /* Unrecognized/unsupported. */
294 i++;
295 break;
296 }
297
298 i = parse_ext_version(isa, i, NULL, NULL);
299 }
300
301 return (0);
302 }
303
304 #ifdef FDT
305 static void
parse_mmu_fdt(struct cpu_desc * desc,phandle_t node)306 parse_mmu_fdt(struct cpu_desc *desc, phandle_t node)
307 {
308 char mmu[16];
309
310 desc->mmu_caps |= MMU_SV39;
311 if (OF_getprop(node, "mmu-type", mmu, sizeof(mmu)) > 0) {
312 if (strcmp(mmu, "riscv,sv48") == 0)
313 desc->mmu_caps |= MMU_SV48;
314 else if (strcmp(mmu, "riscv,sv57") == 0)
315 desc->mmu_caps |= MMU_SV48 | MMU_SV57;
316 }
317 }
318
319 static void
identify_cpu_features_fdt(u_int cpu,struct cpu_desc * desc)320 identify_cpu_features_fdt(u_int cpu, struct cpu_desc *desc)
321 {
322 char isa[1024];
323 phandle_t node;
324 ssize_t len;
325 pcell_t reg;
326 u_int hart;
327
328 node = OF_finddevice("/cpus");
329 if (node == -1) {
330 printf("%s: could not find /cpus node in FDT\n", __func__);
331 return;
332 }
333
334 hart = pcpu_find(cpu)->pc_hart;
335
336 /*
337 * Locate our current CPU's node in the device-tree, and parse its
338 * contents to detect supported CPU/ISA features and extensions.
339 */
340 for (node = OF_child(node); node > 0; node = OF_peer(node)) {
341 /* Skip any non-CPU nodes, such as cpu-map. */
342 if (!ofw_bus_node_is_compatible(node, "riscv"))
343 continue;
344
345 /* Find this CPU */
346 if (OF_getencprop(node, "reg", ®, sizeof(reg)) <= 0 ||
347 reg != hart)
348 continue;
349
350 len = OF_getprop(node, "riscv,isa", isa, sizeof(isa));
351 KASSERT(len <= sizeof(isa), ("ISA string truncated"));
352 if (len == -1) {
353 printf("%s: could not find 'riscv,isa' property "
354 "for CPU %d, hart %u\n", __func__, cpu, hart);
355 return;
356 }
357
358 /*
359 * The string is specified to be lowercase, but let's be
360 * certain.
361 */
362 for (int i = 0; i < len; i++)
363 isa[i] = tolower(isa[i]);
364 if (parse_riscv_isa(desc, isa, len) != 0)
365 return;
366
367 /* Check MMU features. */
368 parse_mmu_fdt(desc, node);
369
370 /* We are done. */
371 break;
372 }
373 if (node <= 0) {
374 printf("%s: could not find FDT node for CPU %u, hart %u\n",
375 __func__, cpu, hart);
376 }
377 }
378 #endif
379
380 static void
identify_cpu_features(u_int cpu,struct cpu_desc * desc)381 identify_cpu_features(u_int cpu, struct cpu_desc *desc)
382 {
383 #ifdef FDT
384 identify_cpu_features_fdt(cpu, desc);
385 #endif
386 }
387
388 /*
389 * Update kernel/user global state based on the feature parsing results, stored
390 * in desc.
391 *
392 * We keep only the subset of values common to all CPUs.
393 */
394 static void
update_global_capabilities(u_int cpu,struct cpu_desc * desc)395 update_global_capabilities(u_int cpu, struct cpu_desc *desc)
396 {
397 #define UPDATE_CAP(t, v) \
398 do { \
399 if (cpu == 0) { \
400 (t) = (v); \
401 } else { \
402 (t) &= (v); \
403 } \
404 } while (0)
405
406 /* Update the capabilities exposed to userspace via AT_HWCAP. */
407 UPDATE_CAP(elf_hwcap, (u_long)desc->isa_extensions);
408
409 /*
410 * MMU capabilities, e.g. Sv48.
411 */
412 UPDATE_CAP(mmu_caps, desc->mmu_caps);
413
414 /* Supervisor-mode extension support. */
415 UPDATE_CAP(has_sstc, (desc->smode_extensions & SV_SSTC) != 0);
416 UPDATE_CAP(has_sscofpmf, (desc->smode_extensions & SV_SSCOFPMF) != 0);
417
418 #undef UPDATE_CAP
419 }
420
421 static void
identify_cpu_ids(struct cpu_desc * desc)422 identify_cpu_ids(struct cpu_desc *desc)
423 {
424 const struct marchid_entry *table = NULL;
425 int i;
426
427 desc->cpu_mvendor_name = "Unknown";
428 desc->cpu_march_name = "Unknown";
429
430 /*
431 * Search for a recognized vendor, and possibly obtain the secondary
432 * table for marchid lookup.
433 */
434 for (i = 0; i < nitems(mvendor_ids); i++) {
435 if (mvendorid == mvendor_ids[i].mvendor_id) {
436 desc->cpu_mvendor_name = mvendor_ids[i].mvendor_name;
437 table = mvendor_ids[i].marchid_table;
438 break;
439 }
440 }
441
442 if (marchid == MARCHID_UNIMPL) {
443 desc->cpu_march_name = "Unspecified";
444 return;
445 }
446
447 if (MARCHID_IS_OPENSOURCE(marchid)) {
448 table = global_marchids;
449 } else if (table == NULL)
450 return;
451
452 for (i = 0; table[i].march_name != NULL; i++) {
453 if (marchid == table[i].march_id) {
454 desc->cpu_march_name = table[i].march_name;
455 break;
456 }
457 }
458 }
459
460 void
identify_cpu(u_int cpu)461 identify_cpu(u_int cpu)
462 {
463 struct cpu_desc *desc = &cpu_desc[cpu];
464
465 identify_cpu_ids(desc);
466 identify_cpu_features(cpu, desc);
467
468 update_global_capabilities(cpu, desc);
469 }
470
471 void
printcpuinfo(u_int cpu)472 printcpuinfo(u_int cpu)
473 {
474 struct cpu_desc *desc;
475 u_int hart;
476
477 desc = &cpu_desc[cpu];
478 hart = pcpu_find(cpu)->pc_hart;
479
480 /* XXX: check this here so we are guaranteed to have console output. */
481 KASSERT(desc->isa_extensions != 0,
482 ("Empty extension set for CPU %u, did parsing fail?", cpu));
483
484 /*
485 * Suppress the output of some fields in the common case of identical
486 * CPU features.
487 */
488 #define SHOULD_PRINT(_field) \
489 (cpu == 0 || desc[0]._field != desc[-1]._field)
490
491 /* Always print summary line. */
492 printf("CPU %-3u: Vendor=%s Core=%s (Hart %u)\n", cpu,
493 desc->cpu_mvendor_name, desc->cpu_march_name, hart);
494
495 /* These values are global. */
496 if (cpu == 0)
497 printf(" marchid=%#lx, mimpid=%#lx\n", marchid, mimpid);
498
499 if (SHOULD_PRINT(mmu_caps)) {
500 printf(" MMU: %#b\n", desc->mmu_caps,
501 "\020"
502 "\01Sv39"
503 "\02Sv48"
504 "\03Sv57");
505 }
506
507 if (SHOULD_PRINT(isa_extensions)) {
508 printf(" ISA: %#b\n", desc->isa_extensions,
509 "\020"
510 "\01Atomic"
511 "\03Compressed"
512 "\04Double"
513 "\06Float"
514 "\15Mult/Div");
515 }
516
517 if (SHOULD_PRINT(smode_extensions)) {
518 printf(" S-mode Extensions: %#b\n", desc->smode_extensions,
519 "\020"
520 "\01Sstc"
521 "\02Svnapot"
522 "\03Svpbmt"
523 "\04Svinval"
524 "\05Sscofpmf");
525 }
526
527 #undef SHOULD_PRINT
528 }
529