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