#include <expand.h>
#include <shared.h>
#ifdef SUPPORT_NETBOOT
#include <grub.h>
#endif
#include <cpu.h>
#define EVF_DEFINED 0x01
#define EVF_VALUESET 0x02
typedef struct variable {
char v_name[EV_NAMELEN];
unsigned int v_flags;
char v_value[220];
} variable_t;
static variable_t expvars[32];
static const unsigned int nexpvars = 32;
int
set_variable(const char *name, const char *value)
{
unsigned int i;
unsigned int avail = nexpvars;
if (strlen(name) >= sizeof (expvars[0].v_name))
return (ERR_WONT_FIT);
if (value != NULL && strlen(value) >= sizeof (expvars[0].v_value))
return (ERR_WONT_FIT);
for (i = 0; i < nexpvars; i++) {
if (expvars[i].v_flags & EVF_DEFINED) {
if (grub_strcmp(expvars[i].v_name, name) == 0)
break;
} else if (i < avail) {
avail = i;
}
}
if (i == nexpvars) {
if (avail == nexpvars)
return (ERR_WONT_FIT);
i = avail;
(void) grub_strcpy(expvars[i].v_name, name);
expvars[i].v_flags = EVF_DEFINED;
}
if (value != NULL) {
(void) grub_strcpy(expvars[i].v_value, value);
expvars[i].v_flags |= EVF_VALUESET;
} else {
expvars[i].v_flags &= ~EVF_VALUESET;
}
return (0);
}
const char *
get_variable(const char *name)
{
unsigned int i;
for (i = 0; i < nexpvars; i++) {
if (!(expvars[i].v_flags & EVF_DEFINED))
continue;
if (grub_strcmp(expvars[i].v_name, name) == 0) {
if (expvars[i].v_flags & EVF_VALUESET)
return (expvars[i].v_value);
return ("");
}
}
return (NULL);
}
static int
detect_target_operating_mode(void)
{
int ret, ah;
ret = get_target_operating_mode();
ah = ret >> 8;
ret &= 0xff;
if (ah == 0x86 && ret != 0) {
grub_printf("[BIOS 'Detect Target Operating Mode' "
"callback unsupported on this platform]\n");
return (1);
}
if (ah == 0 && ret == 0) {
grub_printf("[BIOS accepted mixed-mode target setting!]\n");
return (1);
}
if (ah == 0 && ret != 0) {
grub_printf("fatal: BIOS reports this machine CANNOT run in "
"mixed 32/64-bit mode!\n");
return (0);
}
grub_printf("warning: BIOS Detect Target Operating Mode callback "
"confused.\n %%ax >> 8 = 0x%x, carry = %d\n", ah, ret);
return (1);
}
static int
amd64_config_cpu(void)
{
struct amd64_cpuid_regs __vcr, *vcr = &__vcr;
uint32_t maxeax;
uint32_t max_maxeax = 0x100;
char vendor[13];
int isamd64 = 0;
uint32_t stdfeatures = 0, xtdfeatures = 0;
uint64_t efer;
if (sizeof (uint64_t) != 8)
prom_panic("grub compiled improperly, unable to boot "
"64-bit AMD64 executables");
if (amd64_cpuid_supported() == 0)
return (0);
amd64_cpuid_insn(0, vcr);
maxeax = vcr->r_eax;
{
uint32_t *iptr = (uint32_t *)vendor;
*iptr++ = vcr->r_ebx;
*iptr++ = vcr->r_edx;
*iptr++ = vcr->r_ecx;
vendor[12] = '\0';
}
if (maxeax > max_maxeax) {
grub_printf("cpu: warning, maxeax was 0x%x -> 0x%x\n",
maxeax, max_maxeax);
maxeax = max_maxeax;
}
if (maxeax < 1)
return (0);
else {
uint_t family, model, step;
amd64_cpuid_insn(1, vcr);
if ((family = BITX(vcr->r_eax, 11, 8)) == 0xf)
family += BITX(vcr->r_eax, 27, 20);
if ((model = BITX(vcr->r_eax, 7, 4)) == 0xf)
model += BITX(vcr->r_eax, 19, 16) << 4;
step = BITX(vcr->r_eax, 3, 0);
grub_printf("cpu: '%s' family %d model %d step %d\n",
vendor, family, model, step);
stdfeatures = vcr->r_edx;
}
amd64_cpuid_insn(0x80000000, vcr);
if (vcr->r_eax & 0x80000000) {
uint32_t xmaxeax = vcr->r_eax;
const uint32_t max_xmaxeax = 0x80000100;
if (xmaxeax > max_xmaxeax) {
grub_printf("amd64: warning, xmaxeax was "
"0x%x -> 0x%x\n", xmaxeax, max_xmaxeax);
xmaxeax = max_xmaxeax;
}
if (xmaxeax >= 0x80000001) {
amd64_cpuid_insn(0x80000001, vcr);
xtdfeatures = vcr->r_edx;
}
}
if (BITX(xtdfeatures, 29, 29))
isamd64++;
else
grub_printf("amd64: CPU does NOT support long mode\n");
if (!BITX(stdfeatures, 0, 0)) {
grub_printf("amd64: CPU does NOT support FPU\n");
isamd64--;
}
if (!BITX(stdfeatures, 4, 4)) {
grub_printf("amd64: CPU does NOT support TSC\n");
isamd64--;
}
if (!BITX(stdfeatures, 5, 5)) {
grub_printf("amd64: CPU does NOT support MSRs\n");
isamd64--;
}
if (!BITX(stdfeatures, 6, 6)) {
grub_printf("amd64: CPU does NOT support PAE\n");
isamd64--;
}
if (!BITX(stdfeatures, 8, 8)) {
grub_printf("amd64: CPU does NOT support CX8\n");
isamd64--;
}
if (!BITX(stdfeatures, 13, 13)) {
grub_printf("amd64: CPU does NOT support PGE\n");
isamd64--;
}
if (!BITX(stdfeatures, 19, 19)) {
grub_printf("amd64: CPU does NOT support CLFSH\n");
isamd64--;
}
if (!BITX(stdfeatures, 23, 23)) {
grub_printf("amd64: CPU does NOT support MMX\n");
isamd64--;
}
if (!BITX(stdfeatures, 24, 24)) {
grub_printf("amd64: CPU does NOT support FXSR\n");
isamd64--;
}
if (!BITX(stdfeatures, 25, 25)) {
grub_printf("amd64: CPU does NOT support SSE\n");
isamd64--;
}
if (!BITX(stdfeatures, 26, 26)) {
grub_printf("amd64: CPU does NOT support SSE2\n");
isamd64--;
}
if (isamd64 < 1) {
grub_printf("amd64: CPU does not support amd64 executables.\n");
return (0);
}
amd64_rdmsr(MSR_AMD_EFER, &efer);
if (efer & AMD_EFER_SCE)
grub_printf("amd64: EFER_SCE (syscall/sysret) already "
"enabled\n");
if (efer & AMD_EFER_NXE)
grub_printf("amd64: EFER_NXE (no-exec prot) already enabled\n");
if (efer & AMD_EFER_LME)
grub_printf("amd64: EFER_LME (long mode) already enabled\n");
return (detect_target_operating_mode());
}
static int
isamd64()
{
static int ret = -1;
if (ret == -1)
ret = amd64_config_cpu();
return (ret);
}
static int
check_min_mem64(void)
{
if (min_mem64 == 0)
return (1);
if ((mbi.mem_upper / 10240) * 11 >= min_mem64)
return (1);
return (0);
}
int
expand_string(const char *s, char *d, unsigned int len)
{
unsigned int i;
int vlen;
const char *p;
char *q;
const char *start;
char name[EV_NAMELEN];
const char *val;
for (p = s, q = d; *p != '\0' && q < d + len; ) {
if (grub_strncmp(p, "$ISADIR", 7) == 0) {
if (isamd64() && check_min_mem64()) {
if (q + 5 >= d + len)
return (ERR_WONT_FIT);
(void) grub_memcpy(q, "amd64", 5);
q += 5;
}
p += 7;
continue;
}
if (grub_strncmp(p, "$ZFS-BOOTFS", 11) == 0 &&
is_zfs_mount != 0) {
if (current_bootpath[0] == '\0' &&
current_devid[0] == '\0') {
return (ERR_NO_BOOTPATH);
}
vlen = (current_bootfs_obj > 0) ? 10 : 0;
vlen += 11;
vlen += strlen(current_rootpool);
if (current_bootpath[0] != '\0')
vlen += 12 + strlen(current_bootpath);
if (current_devid[0] != '\0')
vlen += 13 + strlen(current_devid);
if (current_bootguid != 0) {
vlen += grub_sprintf(NULL,
",zfs-bootpool=\"%llu\"", current_bootguid);
}
if (current_bootvdev != 0) {
vlen += grub_sprintf(NULL,
",zfs-bootvdev=\"%llu\"", current_bootvdev);
}
if (q + vlen >= d + len)
return (ERR_WONT_FIT);
if (current_bootfs_obj > 0) {
q += grub_sprintf(q, "zfs-bootfs=%s/%u",
current_rootpool, current_bootfs_obj);
} else {
q += grub_sprintf(q, "zfs-bootfs=%s",
current_rootpool);
}
if (current_bootpath[0] != '\0') {
q += grub_sprintf(q, ",bootpath=\"%s\"",
current_bootpath);
}
if (current_devid[0] != '\0') {
q += grub_sprintf(q, ",diskdevid=\"%s\"",
current_devid);
}
if (current_bootguid != 0) {
q += grub_sprintf(q, ",zfs-bootpool=\"%llu\"",
current_bootguid);
}
if (current_bootvdev != 0) {
q += grub_sprintf(q, ",zfs-bootvdev=\"%llu\"",
current_bootvdev);
}
p += 11;
continue;
}
if (*p == '$' && *(p + 1) == '{') {
start = p + 2;
for (p = start; *p != '\0' && *p != '}' &&
p - start < sizeof (name) - 1; p++) {
name[p - start] = *p;
}
if (p - start >= sizeof (name) - 1 || *p != '}') {
p = start;
*q++ = '$';
*q++ = '{';
continue;
}
name[p - start] = '\0';
val = get_variable(name);
if (val == NULL)
return (ERR_NOVAR);
if ((vlen = grub_strlen(val)) >= q + len - d)
return (ERR_WONT_FIT);
(void) grub_memcpy(q, val, vlen);
q += vlen;
p++;
} else {
*q++ = *p++;
}
}
if (q >= d + len)
return (ERR_WONT_FIT);
*q = '\0';
return (0);
}
void
dump_variables(void)
{
unsigned int i;
for (i = 0; i < nexpvars; i++) {
if (!(expvars[i].v_flags & EVF_DEFINED))
continue;
(void) grub_printf("[%u] '%s' => '%s'\n", i, expvars[i].v_name,
(expvars[i].v_flags & EVF_VALUESET) ?
expvars[i].v_value : "");
}
}