]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/riscv/riscv/identcpu.c
riscv: catch up with EARLY_PRINTF changes
[FreeBSD/FreeBSD.git] / sys / riscv / riscv / identcpu.c
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
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
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
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
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
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
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
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
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
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
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
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
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 }