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", ®, 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 } 529