xref: /freebsd/sys/riscv/riscv/identcpu.c (revision fdafd315)
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", &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