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
set_variable(const char * name,const char * value)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 *
get_variable(const char * name)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
detect_target_operating_mode(void)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
amd64_config_cpu(void)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
isamd64()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
check_min_mem64(void)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
expand_string(const char * s,char * d,unsigned int len)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
dump_variables(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