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
set_variable(const char * name,const char * value)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 *
get_variable(const char * name)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
detect_target_operating_mode(void)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 & 0xff;
112 ret >>= 8;
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
amd64_config_cpu(void)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
isamd64()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
check_min_mem64(void)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
expand_string(const char * s,char * d,unsigned int len)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
dump_variables(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