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
33typedef 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
39static variable_t expvars[32];
40static const unsigned int nexpvars = 32;
41
42int
43set_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
82const char *
83get_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
100static int
101detect_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
137static int
138amd64_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
312static int
313isamd64()
314{
315	static int ret = -1;
316
317	if (ret == -1)
318		ret = amd64_config_cpu();
319
320	return (ret);
321}
322
323static int
324check_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 */
347int
348expand_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
453void
454dump_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