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