1 /* 2 * GRUB -- GRand Unified Bootloader 3 * Copyright (C) 1999,2000,2001,2002,2003,2004 Free Software Foundation, Inc. 4 * Copyright (c) 2013 Joyent, Inc. All rights reserved. 5 * Copyright 2021 RackTop Systems, Inc. 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 20 */ 21 22 #include <expand.h> 23 #include <shared.h> 24 25 #ifdef SUPPORT_NETBOOT 26 #include <grub.h> 27 #endif 28 29 #include <cpu.h> 30 31 #define EVF_DEFINED 0x01 32 #define EVF_VALUESET 0x02 33 34 typedef struct variable { 35 char v_name[EV_NAMELEN]; 36 unsigned int v_flags; 37 char v_value[220]; /* 256 - EV_NAMELEN - sizeof (fields) */ 38 } variable_t; 39 40 static variable_t expvars[32]; 41 static const unsigned int nexpvars = 32; 42 43 int 44 set_variable(const char *name, const char *value) 45 { 46 unsigned int i; 47 unsigned int avail = nexpvars; 48 49 if (strlen(name) >= sizeof (expvars[0].v_name)) 50 return (ERR_WONT_FIT); 51 52 if (value != NULL && strlen(value) >= sizeof (expvars[0].v_value)) 53 return (ERR_WONT_FIT); 54 55 for (i = 0; i < nexpvars; i++) { 56 if (expvars[i].v_flags & EVF_DEFINED) { 57 if (grub_strcmp(expvars[i].v_name, name) == 0) 58 break; 59 } else if (i < avail) { 60 avail = i; 61 } 62 } 63 64 if (i == nexpvars) { 65 if (avail == nexpvars) 66 return (ERR_WONT_FIT); 67 68 i = avail; 69 (void) grub_strcpy(expvars[i].v_name, name); 70 expvars[i].v_flags = EVF_DEFINED; 71 } 72 73 if (value != NULL) { 74 (void) grub_strcpy(expvars[i].v_value, value); 75 expvars[i].v_flags |= EVF_VALUESET; 76 } else { 77 expvars[i].v_flags &= ~EVF_VALUESET; 78 } 79 80 return (0); 81 } 82 83 const char * 84 get_variable(const char *name) 85 { 86 unsigned int i; 87 88 for (i = 0; i < nexpvars; i++) { 89 if (!(expvars[i].v_flags & EVF_DEFINED)) 90 continue; 91 if (grub_strcmp(expvars[i].v_name, name) == 0) { 92 if (expvars[i].v_flags & EVF_VALUESET) 93 return (expvars[i].v_value); 94 return (""); 95 } 96 } 97 98 return (NULL); 99 } 100 101 static int 102 detect_target_operating_mode(void) 103 { 104 int ret, ah; 105 106 /* 107 * This function returns 16 bits. The upper 8 are the value of %ah 108 * after calling int 15/ec00. The lower 8 bits are zero if the BIOS 109 * call left CF clear, nonzero otherwise. 110 */ 111 ret = get_target_operating_mode(); 112 ah = ret >> 8; 113 ret &= 0xff; 114 115 if (ah == 0x86 && ret != 0) { 116 grub_printf("[BIOS 'Detect Target Operating Mode' " 117 "callback unsupported on this platform]\n"); 118 return (1); /* unsupported, ignore */ 119 } 120 121 if (ah == 0 && ret == 0) { 122 grub_printf("[BIOS accepted mixed-mode target setting!]\n"); 123 return (1); /* told the bios what we're up to */ 124 } 125 126 if (ah == 0 && ret != 0) { 127 grub_printf("fatal: BIOS reports this machine CANNOT run in " 128 "mixed 32/64-bit mode!\n"); 129 return (0); 130 } 131 132 grub_printf("warning: BIOS Detect Target Operating Mode callback " 133 "confused.\n %%ax >> 8 = 0x%x, carry = %d\n", ah, ret); 134 135 return (1); 136 } 137 138 static int 139 amd64_config_cpu(void) 140 { 141 struct amd64_cpuid_regs __vcr, *vcr = &__vcr; 142 uint32_t maxeax; 143 uint32_t max_maxeax = 0x100; 144 char vendor[13]; 145 int isamd64 = 0; 146 uint32_t stdfeatures = 0, xtdfeatures = 0; 147 uint64_t efer; 148 149 /* 150 * This check may seem silly, but if the C preprocesor symbol __amd64 151 * is #defined during compilation, something that may outwardly seem 152 * like a good idea, uts/common/sys/isa_defs.h will #define _LP64, 153 * which will cause uts/common/sys/int_types.h to typedef uint64_t as 154 * an unsigned long - which is only 4 bytes in size when using a 32-bit 155 * compiler. 156 * 157 * If that happens, all the page table translation routines will fail 158 * horribly, so check the size of uint64_t just to insure some degree 159 * of sanity in future operations. 160 */ 161 /*LINTED [sizeof result is invarient]*/ 162 if (sizeof (uint64_t) != 8) 163 prom_panic("grub compiled improperly, unable to boot " 164 "64-bit AMD64 executables"); 165 166 /* 167 * If the CPU doesn't support the CPUID instruction, it's definitely 168 * not an AMD64. 169 */ 170 if (amd64_cpuid_supported() == 0) 171 return (0); 172 173 amd64_cpuid_insn(0, vcr); 174 175 maxeax = vcr->r_eax; 176 { 177 /*LINTED [vendor string from cpuid data]*/ 178 uint32_t *iptr = (uint32_t *)vendor; 179 180 *iptr++ = vcr->r_ebx; 181 *iptr++ = vcr->r_edx; 182 *iptr++ = vcr->r_ecx; 183 184 vendor[12] = '\0'; 185 } 186 187 if (maxeax > max_maxeax) { 188 grub_printf("cpu: warning, maxeax was 0x%x -> 0x%x\n", 189 maxeax, max_maxeax); 190 maxeax = max_maxeax; 191 } 192 193 if (maxeax < 1) 194 return (0); /* no additional functions, not an AMD64 */ 195 else { 196 uint_t family, model, step; 197 198 amd64_cpuid_insn(1, vcr); 199 200 /* 201 * All AMD64/IA32e processors technically SHOULD report 202 * themselves as being in family 0xf, but for some reason 203 * Simics doesn't, and this may change in the future, so 204 * don't error out if it's not true. 205 */ 206 if ((family = BITX(vcr->r_eax, 11, 8)) == 0xf) 207 family += BITX(vcr->r_eax, 27, 20); 208 209 if ((model = BITX(vcr->r_eax, 7, 4)) == 0xf) 210 model += BITX(vcr->r_eax, 19, 16) << 4; 211 step = BITX(vcr->r_eax, 3, 0); 212 213 grub_printf("cpu: '%s' family %d model %d step %d\n", 214 vendor, family, model, step); 215 stdfeatures = vcr->r_edx; 216 } 217 218 amd64_cpuid_insn(0x80000000, vcr); 219 220 if (vcr->r_eax & 0x80000000) { 221 uint32_t xmaxeax = vcr->r_eax; 222 const uint32_t max_xmaxeax = 0x80000100; 223 224 if (xmaxeax > max_xmaxeax) { 225 grub_printf("amd64: warning, xmaxeax was " 226 "0x%x -> 0x%x\n", xmaxeax, max_xmaxeax); 227 xmaxeax = max_xmaxeax; 228 } 229 230 if (xmaxeax >= 0x80000001) { 231 amd64_cpuid_insn(0x80000001, vcr); 232 xtdfeatures = vcr->r_edx; 233 } 234 } 235 236 if (BITX(xtdfeatures, 29, 29)) /* long mode */ 237 isamd64++; 238 else 239 grub_printf("amd64: CPU does NOT support long mode\n"); 240 241 if (!BITX(stdfeatures, 0, 0)) { 242 grub_printf("amd64: CPU does NOT support FPU\n"); 243 isamd64--; 244 } 245 246 if (!BITX(stdfeatures, 4, 4)) { 247 grub_printf("amd64: CPU does NOT support TSC\n"); 248 isamd64--; 249 } 250 251 if (!BITX(stdfeatures, 5, 5)) { 252 grub_printf("amd64: CPU does NOT support MSRs\n"); 253 isamd64--; 254 } 255 256 if (!BITX(stdfeatures, 6, 6)) { 257 grub_printf("amd64: CPU does NOT support PAE\n"); 258 isamd64--; 259 } 260 261 if (!BITX(stdfeatures, 8, 8)) { 262 grub_printf("amd64: CPU does NOT support CX8\n"); 263 isamd64--; 264 } 265 266 if (!BITX(stdfeatures, 13, 13)) { 267 grub_printf("amd64: CPU does NOT support PGE\n"); 268 isamd64--; 269 } 270 271 if (!BITX(stdfeatures, 19, 19)) { 272 grub_printf("amd64: CPU does NOT support CLFSH\n"); 273 isamd64--; 274 } 275 276 if (!BITX(stdfeatures, 23, 23)) { 277 grub_printf("amd64: CPU does NOT support MMX\n"); 278 isamd64--; 279 } 280 281 if (!BITX(stdfeatures, 24, 24)) { 282 grub_printf("amd64: CPU does NOT support FXSR\n"); 283 isamd64--; 284 } 285 286 if (!BITX(stdfeatures, 25, 25)) { 287 grub_printf("amd64: CPU does NOT support SSE\n"); 288 isamd64--; 289 } 290 291 if (!BITX(stdfeatures, 26, 26)) { 292 grub_printf("amd64: CPU does NOT support SSE2\n"); 293 isamd64--; 294 } 295 296 if (isamd64 < 1) { 297 grub_printf("amd64: CPU does not support amd64 executables.\n"); 298 return (0); 299 } 300 301 amd64_rdmsr(MSR_AMD_EFER, &efer); 302 if (efer & AMD_EFER_SCE) 303 grub_printf("amd64: EFER_SCE (syscall/sysret) already " 304 "enabled\n"); 305 if (efer & AMD_EFER_NXE) 306 grub_printf("amd64: EFER_NXE (no-exec prot) already enabled\n"); 307 if (efer & AMD_EFER_LME) 308 grub_printf("amd64: EFER_LME (long mode) already enabled\n"); 309 310 return (detect_target_operating_mode()); 311 } 312 313 static int 314 isamd64() 315 { 316 static int ret = -1; 317 318 if (ret == -1) 319 ret = amd64_config_cpu(); 320 321 return (ret); 322 } 323 324 static int 325 check_min_mem64(void) 326 { 327 if (min_mem64 == 0) 328 return (1); 329 330 if ((mbi.mem_upper / 10240) * 11 >= min_mem64) 331 return (1); 332 333 return (0); 334 } 335 336 /* 337 * Given the nul-terminated input string s, expand all variable references 338 * within that string into the buffer pointed to by d, which must be of length 339 * not less than len bytes. 340 * 341 * We also expand the special case tokens "$ISADIR" and "$ZFS-BOOTFS" here. 342 * 343 * If the string will not fit, returns ERR_WONT_FIT. 344 * If a nonexistent variable is referenced, returns ERR_NOVAR. 345 * Otherwise, returns 0. The resulting string is nul-terminated. On error, 346 * the contents of the destination buffer are undefined. 347 */ 348 int 349 expand_string(const char *s, char *d, unsigned int len) 350 { 351 unsigned int i; 352 int vlen; 353 const char *p; 354 char *q; 355 const char *start; 356 char name[EV_NAMELEN]; 357 const char *val; 358 359 for (p = s, q = d; *p != '\0' && q < d + len; ) { 360 /* Special case: $ISADIR */ 361 if (grub_strncmp(p, "$ISADIR", 7) == 0) { 362 if (isamd64() && check_min_mem64()) { 363 if (q + 5 >= d + len) 364 return (ERR_WONT_FIT); 365 (void) grub_memcpy(q, "amd64", 5); 366 q += 5; /* amd64 */ 367 } 368 p += 7; /* $ISADIR */ 369 continue; 370 } 371 /* Special case: $ZFS-BOOTFS */ 372 if (grub_strncmp(p, "$ZFS-BOOTFS", 11) == 0 && 373 is_zfs_mount != 0) { 374 if (current_bootpath[0] == '\0' && 375 current_devid[0] == '\0') { 376 return (ERR_NO_BOOTPATH); 377 } 378 379 /* zfs-bootfs=%s/%u */ 380 vlen = (current_bootfs_obj > 0) ? 10 : 0; 381 vlen += 11; 382 vlen += strlen(current_rootpool); 383 384 /* ,bootpath=\"%s\" */ 385 if (current_bootpath[0] != '\0') 386 vlen += 12 + strlen(current_bootpath); 387 388 /* ,diskdevid=\"%s\" */ 389 if (current_devid[0] != '\0') 390 vlen += 13 + strlen(current_devid); 391 392 if (current_bootguid != 0) { 393 vlen += grub_sprintf(NULL, 394 ",zfs-bootpool=\"%llu\"", current_bootguid); 395 } 396 if (current_bootvdev != 0) { 397 vlen += grub_sprintf(NULL, 398 ",zfs-bootvdev=\"%llu\"", current_bootvdev); 399 } 400 401 if (q + vlen >= d + len) 402 return (ERR_WONT_FIT); 403 404 if (current_bootfs_obj > 0) { 405 q += grub_sprintf(q, "zfs-bootfs=%s/%u", 406 current_rootpool, current_bootfs_obj); 407 } else { 408 q += grub_sprintf(q, "zfs-bootfs=%s", 409 current_rootpool); 410 } 411 if (current_bootpath[0] != '\0') { 412 q += grub_sprintf(q, ",bootpath=\"%s\"", 413 current_bootpath); 414 } 415 if (current_devid[0] != '\0') { 416 q += grub_sprintf(q, ",diskdevid=\"%s\"", 417 current_devid); 418 } 419 if (current_bootguid != 0) { 420 q += grub_sprintf(q, ",zfs-bootpool=\"%llu\"", 421 current_bootguid); 422 } 423 if (current_bootvdev != 0) { 424 q += grub_sprintf(q, ",zfs-bootvdev=\"%llu\"", 425 current_bootvdev); 426 } 427 428 p += 11; /* $ZFS-BOOTFS */ 429 continue; 430 } 431 if (*p == '$' && *(p + 1) == '{') { 432 start = p + 2; 433 for (p = start; *p != '\0' && *p != '}' && 434 p - start < sizeof (name) - 1; p++) { 435 name[p - start] = *p; 436 } 437 /* 438 * Unterminated reference. Copy verbatim. 439 */ 440 if (p - start >= sizeof (name) - 1 || *p != '}') { 441 p = start; 442 *q++ = '$'; 443 *q++ = '{'; 444 continue; 445 } 446 447 name[p - start] = '\0'; 448 val = get_variable(name); 449 if (val == NULL) 450 return (ERR_NOVAR); 451 452 if ((vlen = grub_strlen(val)) >= q + len - d) 453 return (ERR_WONT_FIT); 454 455 (void) grub_memcpy(q, val, vlen); 456 q += vlen; 457 p++; 458 } else { 459 *q++ = *p++; 460 } 461 } 462 463 if (q >= d + len) 464 return (ERR_WONT_FIT); 465 466 *q = '\0'; 467 468 return (0); 469 } 470 471 void 472 dump_variables(void) 473 { 474 unsigned int i; 475 476 for (i = 0; i < nexpvars; i++) { 477 if (!(expvars[i].v_flags & EVF_DEFINED)) 478 continue; 479 (void) grub_printf("[%u] '%s' => '%s'\n", i, expvars[i].v_name, 480 (expvars[i].v_flags & EVF_VALUESET) ? 481 expvars[i].v_value : ""); 482 } 483 } 484