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