xref: /illumos-gate/usr/src/grub/grub-0.97/stage2/expand.c (revision 3ce5372277f4657ad0e52d36c979527c4ca22de2)
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