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