402 lines
11 KiB
C
402 lines
11 KiB
C
/* Export pnvram and some variables for runtime */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2009 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <grub/file.h>
|
|
#include <grub/err.h>
|
|
#include <grub/normal.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/efiemu/efiemu.h>
|
|
#include <grub/efiemu/runtime.h>
|
|
#include <grub/extcmd.h>
|
|
|
|
/* Place for final location of variables */
|
|
static int nvram_handle = 0;
|
|
static int nvramsize_handle = 0;
|
|
static int high_monotonic_count_handle = 0;
|
|
static int timezone_handle = 0;
|
|
static int accuracy_handle = 0;
|
|
static int daylight_handle = 0;
|
|
|
|
/* Temporary place */
|
|
static grub_uint8_t *nvram;
|
|
static grub_size_t nvramsize;
|
|
static grub_uint32_t high_monotonic_count;
|
|
static grub_int16_t timezone;
|
|
static grub_uint8_t daylight;
|
|
static grub_uint32_t accuracy;
|
|
|
|
static const struct grub_arg_option options[] = {
|
|
{"size", 's', 0, "number of bytes to reserve for pseudo NVRAM", 0,
|
|
ARG_TYPE_INT},
|
|
{"high-monotonic-count", 'm', 0,
|
|
"Initial value of high monotonic count", 0, ARG_TYPE_INT},
|
|
{"timezone", 't', 0,
|
|
"Timezone, offset in minutes from GMT", 0, ARG_TYPE_INT},
|
|
{"accuracy", 'a', 0,
|
|
"Accuracy of clock, in 1e-12 units", 0, ARG_TYPE_INT},
|
|
{"daylight", 'd', 0,
|
|
"Daylight value, as per EFI specifications", 0, ARG_TYPE_INT},
|
|
{0, 0, 0, 0, 0, 0}
|
|
};
|
|
|
|
/* Parse signed value */
|
|
static int
|
|
grub_strtosl (char *arg, char **end, int base)
|
|
{
|
|
if (arg[0] == '-')
|
|
return -grub_strtoul (arg + 1, end, base);
|
|
return grub_strtoul (arg, end, base);
|
|
}
|
|
|
|
/* Export stuff for efiemu */
|
|
static grub_err_t
|
|
nvram_set (void * data __attribute__ ((unused)))
|
|
{
|
|
/* Take definitive pointers */
|
|
grub_uint8_t *nvram_def = grub_efiemu_mm_obtain_request (nvram_handle);
|
|
grub_uint32_t *nvramsize_def
|
|
= grub_efiemu_mm_obtain_request (nvramsize_handle);
|
|
grub_uint32_t *high_monotonic_count_def
|
|
= grub_efiemu_mm_obtain_request (high_monotonic_count_handle);
|
|
grub_int16_t *timezone_def
|
|
= grub_efiemu_mm_obtain_request (timezone_handle);
|
|
grub_uint8_t *daylight_def
|
|
= grub_efiemu_mm_obtain_request (daylight_handle);
|
|
grub_uint32_t *accuracy_def
|
|
= grub_efiemu_mm_obtain_request (accuracy_handle);
|
|
|
|
/* Copy to definitive loaction */
|
|
grub_dprintf ("efiemu", "preparing pnvram\n");
|
|
grub_memcpy (nvram_def, nvram, nvramsize);
|
|
*nvramsize_def = nvramsize;
|
|
*high_monotonic_count_def = high_monotonic_count;
|
|
*timezone_def = timezone;
|
|
*daylight_def = daylight;
|
|
*accuracy_def = accuracy;
|
|
|
|
/* Register symbols */
|
|
grub_efiemu_register_symbol ("efiemu_variables", nvram_handle, 0);
|
|
grub_efiemu_register_symbol ("efiemu_varsize", nvramsize_handle, 0);
|
|
grub_efiemu_register_symbol ("efiemu_high_monotonic_count",
|
|
high_monotonic_count_handle, 0);
|
|
grub_efiemu_register_symbol ("efiemu_time_zone", timezone_handle, 0);
|
|
grub_efiemu_register_symbol ("efiemu_time_daylight", daylight_handle, 0);
|
|
grub_efiemu_register_symbol ("efiemu_time_accuracy",
|
|
accuracy_handle, 0);
|
|
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static void
|
|
nvram_unload (void * data __attribute__ ((unused)))
|
|
{
|
|
grub_efiemu_mm_return_request (nvram_handle);
|
|
grub_efiemu_mm_return_request (nvramsize_handle);
|
|
grub_efiemu_mm_return_request (high_monotonic_count_handle);
|
|
grub_efiemu_mm_return_request (timezone_handle);
|
|
grub_efiemu_mm_return_request (accuracy_handle);
|
|
grub_efiemu_mm_return_request (daylight_handle);
|
|
|
|
grub_free (nvram);
|
|
nvram = 0;
|
|
}
|
|
|
|
/* Load the variables file It's in format
|
|
guid1:attr1:name1:data1;
|
|
guid2:attr2:name2:data2;
|
|
...
|
|
Where all fields are in hex
|
|
*/
|
|
static grub_err_t
|
|
read_pnvram (char *filename)
|
|
{
|
|
char *buf, *ptr, *ptr2;
|
|
grub_file_t file;
|
|
grub_size_t size;
|
|
grub_uint8_t *nvramptr = nvram;
|
|
struct efi_variable *efivar;
|
|
grub_size_t guidlen, datalen;
|
|
unsigned i, j;
|
|
|
|
file = grub_file_open (filename);
|
|
if (!file)
|
|
return grub_error (GRUB_ERR_BAD_OS, "couldn't read pnvram");
|
|
size = grub_file_size (file);
|
|
buf = grub_malloc (size + 1);
|
|
if (!buf)
|
|
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't read pnvram");
|
|
if (grub_file_read (file, buf, size) != (grub_ssize_t) size)
|
|
return grub_error (GRUB_ERR_BAD_OS, "couldn't read pnvram");
|
|
buf[size] = 0;
|
|
grub_file_close (file);
|
|
|
|
for (ptr = buf; *ptr; )
|
|
{
|
|
if (grub_isspace (*ptr))
|
|
{
|
|
ptr++;
|
|
continue;
|
|
}
|
|
|
|
efivar = (struct efi_variable *) nvramptr;
|
|
if (nvramptr - nvram + sizeof (struct efi_variable) > nvramsize)
|
|
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
|
|
"file is too large for reserved variable space");
|
|
|
|
nvramptr += sizeof (struct efi_variable);
|
|
|
|
/* look ahow long guid field is*/
|
|
guidlen = 0;
|
|
for (ptr2 = ptr; (grub_isspace (*ptr2)
|
|
|| (*ptr2 >= '0' && *ptr2 <= '9')
|
|
|| (*ptr2 >= 'a' && *ptr2 <= 'f')
|
|
|| (*ptr2 >= 'A' && *ptr2 <= 'F'));
|
|
ptr2++)
|
|
if (!grub_isspace (*ptr2))
|
|
guidlen++;
|
|
guidlen /= 2;
|
|
|
|
/* Read guid */
|
|
if (guidlen != sizeof (efivar->guid))
|
|
{
|
|
grub_free (buf);
|
|
return grub_error (GRUB_ERR_BAD_OS, "can't parse %s", filename);
|
|
}
|
|
for (i = 0; i < 2 * sizeof (efivar->guid); i++)
|
|
{
|
|
int hex = 0;
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
if (*ptr >= '0' && *ptr <= '9')
|
|
hex = *ptr - '0';
|
|
if (*ptr >= 'a' && *ptr <= 'f')
|
|
hex = *ptr - 'a' + 10;
|
|
if (*ptr >= 'A' && *ptr <= 'F')
|
|
hex = *ptr - 'A' + 10;
|
|
|
|
if (i%2 == 0)
|
|
((grub_uint8_t *)&(efivar->guid))[i/2] = hex << 4;
|
|
else
|
|
((grub_uint8_t *)&(efivar->guid))[i/2] |= hex;
|
|
ptr++;
|
|
}
|
|
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
if (*ptr != ':')
|
|
{
|
|
grub_dprintf ("efiemu", "Not colon\n");
|
|
grub_free (buf);
|
|
return grub_error (GRUB_ERR_BAD_OS, "can't parse %s", filename);
|
|
}
|
|
ptr++;
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
|
|
/* Attributes can be just parsed by existing functions */
|
|
efivar->attributes = grub_strtoul (ptr, &ptr, 16);
|
|
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
if (*ptr != ':')
|
|
{
|
|
grub_dprintf ("efiemu", "Not colon\n");
|
|
grub_free (buf);
|
|
return grub_error (GRUB_ERR_BAD_OS, "can't parse %s", filename);
|
|
}
|
|
ptr++;
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
|
|
/* Read name and value */
|
|
for (j = 0; j < 2; j++)
|
|
{
|
|
/* Look the length */
|
|
datalen = 0;
|
|
for (ptr2 = ptr; *ptr2 && (grub_isspace (*ptr2)
|
|
|| (*ptr2 >= '0' && *ptr2 <= '9')
|
|
|| (*ptr2 >= 'a' && *ptr2 <= 'f')
|
|
|| (*ptr2 >= 'A' && *ptr2 <= 'F'));
|
|
ptr2++)
|
|
if (!grub_isspace (*ptr2))
|
|
datalen++;
|
|
datalen /= 2;
|
|
|
|
if (nvramptr - nvram + datalen > nvramsize)
|
|
{
|
|
grub_free (buf);
|
|
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
|
|
"file is too large for reserved "
|
|
" variable space");
|
|
}
|
|
|
|
for (i = 0; i < 2 * datalen; i++)
|
|
{
|
|
int hex = 0;
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
if (*ptr >= '0' && *ptr <= '9')
|
|
hex = *ptr - '0';
|
|
if (*ptr >= 'a' && *ptr <= 'f')
|
|
hex = *ptr - 'a' + 10;
|
|
if (*ptr >= 'A' && *ptr <= 'F')
|
|
hex = *ptr - 'A' + 10;
|
|
|
|
if (i%2 == 0)
|
|
nvramptr[i/2] = hex << 4;
|
|
else
|
|
nvramptr[i/2] |= hex;
|
|
ptr++;
|
|
}
|
|
nvramptr += datalen;
|
|
while (grub_isspace (*ptr))
|
|
ptr++;
|
|
if (*ptr != (j ? ';' : ':'))
|
|
{
|
|
grub_free (buf);
|
|
grub_dprintf ("efiemu", j?"Not semicolon\n":"Not colon\n");
|
|
return grub_error (GRUB_ERR_BAD_OS, "can't parse %s", filename);
|
|
}
|
|
if (j)
|
|
efivar->size = datalen;
|
|
else
|
|
efivar->namelen = datalen;
|
|
|
|
ptr++;
|
|
}
|
|
}
|
|
grub_free (buf);
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_efiemu_make_nvram (void)
|
|
{
|
|
grub_err_t err;
|
|
|
|
err = grub_efiemu_autocore ();
|
|
if (err)
|
|
{
|
|
grub_free (nvram);
|
|
return err;
|
|
}
|
|
|
|
err = grub_efiemu_register_prepare_hook (nvram_set, nvram_unload, 0);
|
|
if (err)
|
|
{
|
|
grub_free (nvram);
|
|
return err;
|
|
}
|
|
nvram_handle
|
|
= grub_efiemu_request_memalign (1, nvramsize,
|
|
GRUB_EFI_RUNTIME_SERVICES_DATA);
|
|
nvramsize_handle
|
|
= grub_efiemu_request_memalign (1, sizeof (grub_uint32_t),
|
|
GRUB_EFI_RUNTIME_SERVICES_DATA);
|
|
high_monotonic_count_handle
|
|
= grub_efiemu_request_memalign (1, sizeof (grub_uint32_t),
|
|
GRUB_EFI_RUNTIME_SERVICES_DATA);
|
|
timezone_handle
|
|
= grub_efiemu_request_memalign (1, sizeof (grub_uint16_t),
|
|
GRUB_EFI_RUNTIME_SERVICES_DATA);
|
|
daylight_handle
|
|
= grub_efiemu_request_memalign (1, sizeof (grub_uint8_t),
|
|
GRUB_EFI_RUNTIME_SERVICES_DATA);
|
|
accuracy_handle
|
|
= grub_efiemu_request_memalign (1, sizeof (grub_uint32_t),
|
|
GRUB_EFI_RUNTIME_SERVICES_DATA);
|
|
|
|
grub_efiemu_request_symbols (6);
|
|
return GRUB_ERR_NONE;
|
|
}
|
|
|
|
grub_err_t
|
|
grub_efiemu_pnvram (void)
|
|
{
|
|
if (nvram)
|
|
return GRUB_ERR_NONE;
|
|
|
|
nvramsize = 2048;
|
|
high_monotonic_count = 1;
|
|
timezone = GRUB_EFI_UNSPECIFIED_TIMEZONE;
|
|
accuracy = 50000000;
|
|
daylight = 0;
|
|
|
|
nvram = grub_malloc (nvramsize);
|
|
if (!nvram)
|
|
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
|
|
"Couldn't allocate space for temporary pnvram storage");
|
|
grub_memset (nvram, 0, nvramsize);
|
|
|
|
return grub_efiemu_make_nvram ();
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_cmd_efiemu_pnvram (struct grub_extcmd *cmd,
|
|
int argc, char **args)
|
|
{
|
|
struct grub_arg_list *state = cmd->state;
|
|
grub_err_t err;
|
|
|
|
if (argc > 1)
|
|
return grub_error (GRUB_ERR_BAD_ARGUMENT, "only one argument expected");
|
|
|
|
nvramsize = state[0].set ? grub_strtoul (state[0].arg, 0, 0) : 2048;
|
|
high_monotonic_count = state[1].set ? grub_strtoul (state[1].arg, 0, 0) : 1;
|
|
timezone = state[2].set ? grub_strtosl (state[2].arg, 0, 0)
|
|
: GRUB_EFI_UNSPECIFIED_TIMEZONE;
|
|
accuracy = state[3].set ? grub_strtoul (state[3].arg, 0, 0) : 50000000;
|
|
daylight = state[4].set ? grub_strtoul (state[4].arg, 0, 0) : 0;
|
|
|
|
nvram = grub_malloc (nvramsize);
|
|
if (!nvram)
|
|
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
|
|
"Couldn't allocate space for temporary pnvram storage");
|
|
grub_memset (nvram, 0, nvramsize);
|
|
|
|
if (argc == 1 && (err = read_pnvram (args[0])))
|
|
{
|
|
grub_free (nvram);
|
|
return err;
|
|
}
|
|
return grub_efiemu_make_nvram ();
|
|
}
|
|
|
|
static grub_extcmd_t cmd;
|
|
|
|
void grub_efiemu_pnvram_cmd_register (void);
|
|
void grub_efiemu_pnvram_cmd_unregister (void);
|
|
|
|
void
|
|
grub_efiemu_pnvram_cmd_register (void)
|
|
{
|
|
cmd = grub_register_extcmd ("efiemu_pnvram", grub_cmd_efiemu_pnvram,
|
|
GRUB_COMMAND_FLAG_BOTH,
|
|
"efiemu_pnvram [FILENAME]",
|
|
"Initialise pseudo-NVRAM and load variables "
|
|
"from FILE",
|
|
options);
|
|
}
|
|
|
|
void
|
|
grub_efiemu_pnvram_cmd_unregister (void)
|
|
{
|
|
grub_unregister_extcmd (cmd);
|
|
}
|