copy files need mod for loader: Drop argv[] argument in grub_initrd_load()

Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
This commit is contained in:
Wentao Guan
2024-08-25 17:38:37 +08:00
parent 680df77687
commit 26a4e6f96f
8 changed files with 4033 additions and 0 deletions

View File

@@ -0,0 +1,509 @@
/* linux.c - boot Linux */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2013 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/dl.h>
#include <grub/fdt.h>
#include <grub/file.h>
#include <grub/loader.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/command.h>
#include <grub/cache.h>
#include <grub/cpu/linux.h>
#include <grub/lib/cmdline.h>
#include <grub/linux.h>
#include <grub/verify.h>
GRUB_MOD_LICENSE ("GPLv3+");
static grub_dl_t my_mod;
static grub_addr_t initrd_start;
static grub_addr_t initrd_end;
static grub_addr_t linux_addr;
static grub_size_t linux_size;
static char *linux_args;
static grub_uint32_t machine_type;
static const void *current_fdt;
typedef void (*kernel_entry_t) (int, unsigned long, void *);
#define LINUX_PHYS_OFFSET (0x00008000)
#define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x03000000)
#define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000)
static grub_size_t
get_atag_size (const grub_uint32_t *atag)
{
const grub_uint32_t *atag0 = atag;
while (atag[0] && atag[1])
atag += atag[0];
return atag - atag0;
}
/*
* linux_prepare_fdt():
* Prepares a loaded FDT for being passed to Linux.
* Merges in command line parameters and sets up initrd addresses.
*/
static grub_err_t
linux_prepare_atag (void *target_atag)
{
const grub_uint32_t *atag_orig = (const grub_uint32_t *) current_fdt;
grub_uint32_t *tmp_atag, *to;
const grub_uint32_t *from;
grub_size_t tmp_size;
grub_size_t arg_size = grub_strlen (linux_args);
char *cmdline_orig = NULL;
grub_size_t cmdline_orig_len = 0;
/* some place for cmdline, initrd and terminator. */
tmp_size = get_atag_size (atag_orig) + 20 + (arg_size) / 4;
tmp_atag = grub_malloc (tmp_size * sizeof (grub_uint32_t));
if (!tmp_atag)
return grub_errno;
for (from = atag_orig, to = tmp_atag; from[0] && from[1];
from += from[0])
switch (from[1])
{
case 0x54410004:
case 0x54410005:
case 0x54420005:
break;
case 0x54410009:
if (*(char *) (from + 2))
{
cmdline_orig = (char *) (from + 2);
cmdline_orig_len = grub_strlen (cmdline_orig) + 1;
}
break;
default:
grub_memcpy (to, from, sizeof (grub_uint32_t) * from[0]);
to += from[0];
break;
}
grub_dprintf ("linux", "linux inherited args: '%s'\n",
cmdline_orig ? : "");
grub_dprintf ("linux", "linux_args: '%s'\n", linux_args);
/* Generate and set command line */
to[0] = 3 + (arg_size + cmdline_orig_len) / 4;
to[1] = 0x54410009;
if (cmdline_orig)
{
grub_memcpy ((char *) to + 8, cmdline_orig, cmdline_orig_len - 1);
*((char *) to + 8 + cmdline_orig_len - 1) = ' ';
}
grub_memcpy ((char *) to + 8 + cmdline_orig_len, linux_args, arg_size);
grub_memset ((char *) to + 8 + cmdline_orig_len + arg_size, 0,
4 - ((arg_size + cmdline_orig_len) & 3));
to += to[0];
if (initrd_start && initrd_end)
{
/*
* We're using physical addresses, so even if we have LPAE, we're
* restricted to a 32-bit address space.
*/
grub_dprintf ("loader", "Initrd @ 0x%08x-0x%08x\n",
initrd_start, initrd_end);
to[0] = 4;
to[1] = 0x54420005;
to[2] = initrd_start;
to[3] = initrd_end - initrd_start;
to += 4;
}
to[0] = 0;
to[1] = 0;
to += 2;
/* Copy updated FDT to its launch location */
grub_memcpy (target_atag, tmp_atag, sizeof (grub_uint32_t) * (to - tmp_atag));
grub_free (tmp_atag);
grub_dprintf ("loader", "ATAG updated for Linux boot\n");
return GRUB_ERR_NONE;
}
/*
* linux_prepare_fdt():
* Prepares a loaded FDT for being passed to Linux.
* Merges in command line parameters and sets up initrd addresses.
*/
static grub_err_t
linux_prepare_fdt (void *target_fdt)
{
int node;
int retval;
int tmp_size;
void *tmp_fdt;
tmp_size = grub_fdt_get_totalsize (current_fdt) + 0x100 + grub_strlen (linux_args);
tmp_fdt = grub_malloc (tmp_size);
if (!tmp_fdt)
return grub_errno;
grub_memcpy (tmp_fdt, current_fdt, grub_fdt_get_totalsize (current_fdt));
grub_fdt_set_totalsize (tmp_fdt, tmp_size);
/* Find or create '/chosen' node */
node = grub_fdt_find_subnode (tmp_fdt, 0, "chosen");
if (node < 0)
{
grub_dprintf ("linux", "No 'chosen' node in FDT - creating.\n");
node = grub_fdt_add_subnode (tmp_fdt, 0, "chosen");
if (node < 0)
goto failure;
}
grub_dprintf ("linux", "linux_args: '%s'\n", linux_args);
/* Generate and set command line */
retval = grub_fdt_set_prop (tmp_fdt, node, "bootargs", linux_args,
grub_strlen (linux_args) + 1);
if (retval)
goto failure;
if (initrd_start && initrd_end)
{
/*
* We're using physical addresses, so even if we have LPAE, we're
* restricted to a 32-bit address space.
*/
grub_dprintf ("loader", "Initrd @ 0x%08x-0x%08x\n",
initrd_start, initrd_end);
retval = grub_fdt_set_prop32 (tmp_fdt, node, "linux,initrd-start",
initrd_start);
if (retval)
goto failure;
retval = grub_fdt_set_prop32 (tmp_fdt, node, "linux,initrd-end",
initrd_end);
if (retval)
goto failure;
}
/* Copy updated FDT to its launch location */
grub_memcpy (target_fdt, tmp_fdt, tmp_size);
grub_free (tmp_fdt);
grub_dprintf ("loader", "FDT updated for Linux boot\n");
return GRUB_ERR_NONE;
failure:
grub_free (tmp_fdt);
return grub_error (GRUB_ERR_BAD_ARGUMENT, "unable to prepare FDT");
}
static grub_err_t
linux_boot (void)
{
kernel_entry_t linuxmain;
int fdt_valid, atag_valid;
void *target_fdt = 0;
fdt_valid = (current_fdt && grub_fdt_check_header_nosize (current_fdt) == 0);
atag_valid = ((((const grub_uint16_t *) current_fdt)[3] & ~3) == 0x5440
&& *((const grub_uint32_t *) current_fdt));
grub_dprintf ("loader", "atag: %p, %x, %x, %s, %s\n",
current_fdt,
((const grub_uint16_t *) current_fdt)[3],
*((const grub_uint32_t *) current_fdt),
(const char *) current_fdt,
(const char *) current_fdt + 1);
if (!fdt_valid && machine_type == GRUB_ARM_MACHINE_TYPE_FDT)
return grub_error (GRUB_ERR_FILE_NOT_FOUND,
N_("device tree must be supplied (see `devicetree' command)"));
grub_arch_sync_caches ((void *) linux_addr, linux_size);
grub_dprintf ("loader", "Kernel at: 0x%x\n", linux_addr);
if (fdt_valid || atag_valid)
{
#ifdef GRUB_MACHINE_EFI
grub_size_t size;
if (fdt_valid)
size = grub_fdt_get_totalsize (current_fdt);
else
size = 4 * get_atag_size (current_fdt);
size += grub_strlen (linux_args) + 256;
target_fdt = grub_efi_allocate_loader_memory (LINUX_FDT_PHYS_OFFSET, size);
if (!target_fdt)
return grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
#else
target_fdt = (void *) LINUX_FDT_ADDRESS;
#endif
}
if (fdt_valid)
{
grub_err_t err;
err = linux_prepare_fdt (target_fdt);
if (err)
return err;
grub_dprintf ("loader", "FDT @ %p\n", target_fdt);
}
else if (atag_valid)
{
grub_err_t err;
err = linux_prepare_atag (target_fdt);
if (err)
return err;
grub_dprintf ("loader", "ATAG @ %p\n", target_fdt);
}
grub_dprintf ("loader", "Jumping to Linux...\n");
/* Boot the kernel.
* Arguments to kernel:
* r0 - 0
* r1 - machine type
* r2 - address of DTB
*/
linuxmain = (kernel_entry_t) linux_addr;
grub_arm_disable_caches_mmu ();
linuxmain (0, machine_type, target_fdt);
return grub_error (GRUB_ERR_BAD_OS, "Linux call returned");
}
/*
* Only support zImage, so no relocations necessary
*/
static grub_err_t
linux_load (const char *filename, grub_file_t file)
{
struct linux_arm_kernel_header *lh;
int size;
size = grub_file_size (file);
linux_addr = LINUX_ADDRESS;
grub_dprintf ("loader", "Loading Linux to 0x%08x\n",
(grub_addr_t) linux_addr);
if (grub_file_read (file, (void *) linux_addr, size) != size)
{
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
filename);
return grub_errno;
}
lh = (void *) linux_addr;
if ((grub_size_t) size > sizeof (*lh) &&
lh->magic == GRUB_LINUX_ARM_MAGIC_SIGNATURE)
;
else if (size > 0x8000 && *(grub_uint32_t *) (linux_addr) == 0xea000006
&& machine_type == GRUB_ARM_MACHINE_TYPE_RASPBERRY_PI)
grub_memmove ((void *) linux_addr, (void *) (linux_addr + 0x8000),
size - 0x8000);
else
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid zImage"));
linux_size = size;
return GRUB_ERR_NONE;
}
static grub_err_t
linux_unload (void)
{
grub_dl_unref (my_mod);
grub_free (linux_args);
linux_args = NULL;
initrd_start = initrd_end = 0;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
int size;
grub_err_t err;
grub_file_t file;
grub_dl_ref (my_mod);
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (!file)
goto fail;
err = linux_load (argv[0], file);
grub_file_close (file);
if (err)
goto fail;
grub_loader_set (linux_boot, linux_unload, 0);
size = grub_loader_cmdline_size (argc, argv);
linux_args = grub_malloc (size + sizeof (LINUX_IMAGE));
if (!linux_args)
{
grub_loader_unset();
goto fail;
}
/* Create kernel command line. */
grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE));
err = grub_create_loader_cmdline (argc, argv,
linux_args + sizeof (LINUX_IMAGE) - 1, size,
GRUB_VERIFY_KERNEL_CMDLINE);
if (err)
goto fail;
return GRUB_ERR_NONE;
fail:
grub_dl_unref (my_mod);
return grub_errno;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t file;
grub_size_t size = 0;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_INITRD);
if (!file)
return grub_errno;
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
initrd_start = LINUX_INITRD_ADDRESS;
grub_dprintf ("loader", "Loading initrd to 0x%08x\n",
(grub_addr_t) initrd_start);
if (grub_initrd_load (&initrd_ctx, argv, (void *) initrd_start))
goto fail;
initrd_end = initrd_start + size;
return GRUB_ERR_NONE;
fail:
grub_file_close (file);
return grub_errno;
}
static grub_err_t
load_dtb (grub_file_t dtb, int size)
{
void *new_fdt = grub_zalloc (size);
if (!new_fdt)
return grub_errno;
grub_dprintf ("loader", "Loading device tree to %p\n",
new_fdt);
if ((grub_file_read (dtb, new_fdt, size) != size)
|| (grub_fdt_check_header (new_fdt, size) != 0))
{
grub_free (new_fdt);
return grub_error (GRUB_ERR_BAD_OS, N_("invalid device tree"));
}
grub_fdt_set_totalsize (new_fdt, size);
current_fdt = new_fdt;
/*
* We've successfully loaded an FDT, so any machine type passed
* from firmware is now obsolete.
*/
machine_type = GRUB_ARM_MACHINE_TYPE_FDT;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_cmd_devicetree (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t dtb;
int size;
if (argc != 1)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
dtb = grub_file_open (argv[0], GRUB_FILE_TYPE_DEVICE_TREE_IMAGE);
if (!dtb)
return grub_errno;
size = grub_file_size (dtb);
if (size == 0)
grub_error (GRUB_ERR_BAD_OS, "empty file");
else
load_dtb (dtb, size);
grub_file_close (dtb);
return grub_errno;
}
static grub_command_t cmd_linux, cmd_initrd, cmd_devicetree;
GRUB_MOD_INIT (linux)
{
cmd_linux = grub_register_command ("linux", grub_cmd_linux,
0, N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
0, N_("Load initrd."));
cmd_devicetree = grub_register_command ("devicetree", grub_cmd_devicetree,
/* TRANSLATORS: DTB stands for device tree blob. */
0, N_("Load DTB file."));
my_mod = mod;
current_fdt = (const void *) grub_arm_firmware_get_boot_data ();
machine_type = grub_arm_firmware_get_machine_type ();
}
GRUB_MOD_FINI (linux)
{
grub_unregister_command (cmd_linux);
grub_unregister_command (cmd_initrd);
grub_unregister_command (cmd_devicetree);
}

View File

@@ -0,0 +1,490 @@
/* linux.c - boot Linux zImage or bzImage */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2007,2008,2009,2010 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/loader.h>
#include <grub/file.h>
#include <grub/err.h>
#include <grub/device.h>
#include <grub/disk.h>
#include <grub/misc.h>
#include <grub/types.h>
#include <grub/memory.h>
#include <grub/dl.h>
#include <grub/cpu/linux.h>
#include <grub/command.h>
#include <grub/i18n.h>
#include <grub/mm.h>
#include <grub/cpu/relocator.h>
#include <grub/video.h>
#include <grub/i386/floppy.h>
#include <grub/lib/cmdline.h>
#include <grub/linux.h>
GRUB_MOD_LICENSE ("GPLv3+");
#define GRUB_LINUX_CL_OFFSET 0x9000
static grub_dl_t my_mod;
static grub_size_t linux_mem_size;
static int loaded;
static struct grub_relocator *relocator = NULL;
static grub_addr_t grub_linux_real_target;
static char *grub_linux_real_chunk;
static grub_size_t grub_linux16_prot_size;
static grub_size_t maximal_cmdline_size;
static grub_err_t
grub_linux16_boot (void)
{
grub_uint16_t segment;
struct grub_relocator16_state state;
segment = grub_linux_real_target >> 4;
state.gs = state.fs = state.es = state.ds = state.ss = segment;
state.sp = GRUB_LINUX_SETUP_STACK;
state.cs = segment + 0x20;
state.ip = 0;
state.a20 = 1;
grub_video_set_mode ("text", 0, 0);
grub_stop_floppy ();
return grub_relocator16_boot (relocator, state);
}
static grub_err_t
grub_linux_unload (void)
{
grub_dl_unref (my_mod);
loaded = 0;
grub_relocator_unload (relocator);
relocator = NULL;
return GRUB_ERR_NONE;
}
static int
target_hook (grub_uint64_t addr, grub_uint64_t size, grub_memory_type_t type,
void *data)
{
grub_uint64_t *result = data;
grub_uint64_t candidate;
if (type != GRUB_MEMORY_AVAILABLE)
return 0;
if (addr >= 0xa0000)
return 0;
if (addr + size >= 0xa0000)
size = 0xa0000 - addr;
/* Put the real mode part at as a high location as possible. */
candidate = addr + size - (GRUB_LINUX_CL_OFFSET + maximal_cmdline_size);
/* But it must not exceed the traditional area. */
if (candidate > GRUB_LINUX_OLD_REAL_MODE_ADDR)
candidate = GRUB_LINUX_OLD_REAL_MODE_ADDR;
if (candidate < addr)
return 0;
if (candidate > *result || *result == (grub_uint64_t) -1)
*result = candidate;
return 0;
}
static grub_addr_t
grub_find_real_target (void)
{
grub_uint64_t result = (grub_uint64_t) -1;
grub_mmap_iterate (target_hook, &result);
return result;
}
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t file = 0;
struct linux_i386_kernel_header lh;
grub_uint8_t setup_sects;
grub_size_t real_size;
grub_ssize_t len;
int i;
char *grub_linux_prot_chunk;
int grub_linux_is_bzimage;
grub_addr_t grub_linux_prot_target;
grub_err_t err;
grub_dl_ref (my_mod);
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (! file)
goto fail;
if (grub_file_read (file, &lh, sizeof (lh)) != sizeof (lh))
{
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
argv[0]);
goto fail;
}
if (lh.boot_flag != grub_cpu_to_le16_compile_time (0xaa55))
{
grub_error (GRUB_ERR_BAD_OS, "invalid magic number");
goto fail;
}
if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS)
{
grub_error (GRUB_ERR_BAD_OS, "too many setup sectors");
goto fail;
}
grub_linux_is_bzimage = 0;
setup_sects = lh.setup_sects;
linux_mem_size = 0;
maximal_cmdline_size = 256;
if (lh.header == grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE)
&& grub_le_to_cpu16 (lh.version) >= 0x0200)
{
grub_linux_is_bzimage = (lh.loadflags & GRUB_LINUX_FLAG_BIG_KERNEL);
lh.type_of_loader = GRUB_LINUX_BOOT_LOADER_TYPE;
if (grub_le_to_cpu16 (lh.version) >= 0x0206)
maximal_cmdline_size = grub_le_to_cpu32 (lh.cmdline_size) + 1;
grub_linux_real_target = grub_find_real_target ();
if (grub_linux_real_target == (grub_addr_t)-1)
{
grub_error (GRUB_ERR_OUT_OF_RANGE,
"no appropriate low memory found");
goto fail;
}
if (grub_le_to_cpu16 (lh.version) >= 0x0201)
{
lh.heap_end_ptr = grub_cpu_to_le16_compile_time (GRUB_LINUX_HEAP_END_OFFSET);
lh.loadflags |= GRUB_LINUX_FLAG_CAN_USE_HEAP;
}
if (grub_le_to_cpu16 (lh.version) >= 0x0202)
lh.cmd_line_ptr = grub_linux_real_target + GRUB_LINUX_CL_OFFSET;
else
{
lh.cl_magic = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_MAGIC);
lh.cl_offset = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_OFFSET);
lh.setup_move_size = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_OFFSET
+ maximal_cmdline_size);
}
}
else
{
/* Your kernel is quite old... */
lh.cl_magic = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_MAGIC);
lh.cl_offset = grub_cpu_to_le16_compile_time (GRUB_LINUX_CL_OFFSET);
setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS;
grub_linux_real_target = GRUB_LINUX_OLD_REAL_MODE_ADDR;
}
/* If SETUP_SECTS is not set, set it to the default (4). */
if (! setup_sects)
setup_sects = GRUB_LINUX_DEFAULT_SETUP_SECTS;
real_size = setup_sects << GRUB_DISK_SECTOR_BITS;
grub_linux16_prot_size = grub_file_size (file)
- real_size - GRUB_DISK_SECTOR_SIZE;
if (! grub_linux_is_bzimage
&& GRUB_LINUX_ZIMAGE_ADDR + grub_linux16_prot_size
> grub_linux_real_target)
{
grub_error (GRUB_ERR_BAD_OS, "too big zImage (0x%x > 0x%x), use bzImage instead",
(char *) GRUB_LINUX_ZIMAGE_ADDR + grub_linux16_prot_size,
(grub_size_t) grub_linux_real_target);
goto fail;
}
grub_dprintf ("linux", "[Linux-%s, setup=0x%x, size=0x%x]\n",
grub_linux_is_bzimage ? "bzImage" : "zImage",
(unsigned) real_size,
(unsigned) grub_linux16_prot_size);
for (i = 1; i < argc; i++)
if (grub_memcmp (argv[i], "vga=", 4) == 0)
{
/* Video mode selection support. */
grub_uint16_t vid_mode;
char *val = argv[i] + 4;
if (grub_strcmp (val, "normal") == 0)
vid_mode = GRUB_LINUX_VID_MODE_NORMAL;
else if (grub_strcmp (val, "ext") == 0)
vid_mode = GRUB_LINUX_VID_MODE_EXTENDED;
else if (grub_strcmp (val, "ask") == 0)
vid_mode = GRUB_LINUX_VID_MODE_ASK;
else
vid_mode = (grub_uint16_t) grub_strtoul (val, 0, 0);
if (grub_errno)
goto fail;
lh.vid_mode = grub_cpu_to_le16 (vid_mode);
}
else if (grub_memcmp (argv[i], "mem=", 4) == 0)
{
char *val = argv[i] + 4;
linux_mem_size = grub_strtoul (val, &val, 0);
if (grub_errno)
{
grub_errno = GRUB_ERR_NONE;
linux_mem_size = 0;
}
else
{
int shift = 0;
switch (grub_tolower (val[0]))
{
case 'g':
shift += 10;
/* Fallthrough. */
case 'm':
shift += 10;
/* Fallthrough. */
case 'k':
shift += 10;
/* Fallthrough. */
default:
break;
}
/* Check an overflow. */
if (linux_mem_size > (~0UL >> shift))
linux_mem_size = 0;
else
linux_mem_size <<= shift;
}
}
relocator = grub_relocator_new ();
if (!relocator)
goto fail;
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_addr (relocator, &ch,
grub_linux_real_target,
GRUB_LINUX_CL_OFFSET
+ maximal_cmdline_size);
if (err)
return err;
grub_linux_real_chunk = get_virtual_current_address (ch);
}
/* Put the real mode code at the temporary address. */
grub_memmove (grub_linux_real_chunk, &lh, sizeof (lh));
len = real_size + GRUB_DISK_SECTOR_SIZE - sizeof (lh);
if (grub_file_read (file, grub_linux_real_chunk + sizeof (lh), len) != len)
{
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
argv[0]);
goto fail;
}
if (lh.header != grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE)
|| grub_le_to_cpu16 (lh.version) < 0x0200)
/* Clear the heap space. */
grub_memset (grub_linux_real_chunk
+ ((setup_sects + 1) << GRUB_DISK_SECTOR_BITS),
0,
((GRUB_LINUX_MAX_SETUP_SECTS - setup_sects - 1)
<< GRUB_DISK_SECTOR_BITS));
/* Create kernel command line. */
grub_memcpy ((char *)grub_linux_real_chunk + GRUB_LINUX_CL_OFFSET,
LINUX_IMAGE, sizeof (LINUX_IMAGE));
err = grub_create_loader_cmdline (argc, argv,
(char *)grub_linux_real_chunk
+ GRUB_LINUX_CL_OFFSET + sizeof (LINUX_IMAGE) - 1,
maximal_cmdline_size
- (sizeof (LINUX_IMAGE) - 1),
GRUB_VERIFY_KERNEL_CMDLINE);
if (err)
goto fail;
if (grub_linux_is_bzimage)
grub_linux_prot_target = GRUB_LINUX_BZIMAGE_ADDR;
else
grub_linux_prot_target = GRUB_LINUX_ZIMAGE_ADDR;
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_addr (relocator, &ch,
grub_linux_prot_target,
grub_linux16_prot_size);
if (err)
return err;
grub_linux_prot_chunk = get_virtual_current_address (ch);
}
len = grub_linux16_prot_size;
if (grub_file_read (file, grub_linux_prot_chunk, len) != len && !grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
argv[0]);
if (grub_errno == GRUB_ERR_NONE)
{
grub_loader_set (grub_linux16_boot, grub_linux_unload, 0);
loaded = 1;
}
fail:
if (file)
grub_file_close (file);
if (grub_errno != GRUB_ERR_NONE)
{
grub_dl_unref (my_mod);
loaded = 0;
grub_relocator_unload (relocator);
}
return grub_errno;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_size_t size = 0;
grub_addr_t addr_max, addr_min;
struct linux_i386_kernel_header *lh;
grub_uint8_t *initrd_chunk;
grub_addr_t initrd_addr;
grub_err_t err;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
if (!loaded)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
goto fail;
}
lh = (struct linux_i386_kernel_header *) grub_linux_real_chunk;
if (!(lh->header == grub_cpu_to_le32_compile_time (GRUB_LINUX_I386_MAGIC_SIGNATURE)
&& grub_le_to_cpu16 (lh->version) >= 0x0200))
{
grub_error (GRUB_ERR_BAD_OS, "the kernel is too old for initrd");
goto fail;
}
/* Get the highest address available for the initrd. */
if (grub_le_to_cpu16 (lh->version) >= 0x0203)
{
addr_max = grub_cpu_to_le32 (lh->initrd_addr_max);
/* XXX in reality, Linux specifies a bogus value, so
it is necessary to make sure that ADDR_MAX does not exceed
0x3fffffff. */
if (addr_max > GRUB_LINUX_INITRD_MAX_ADDRESS)
addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
}
else
addr_max = GRUB_LINUX_INITRD_MAX_ADDRESS;
if (linux_mem_size != 0 && linux_mem_size < addr_max)
addr_max = linux_mem_size;
/* Linux 2.3.xx has a bug in the memory range check, so avoid
the last page.
Linux 2.2.xx has a bug in the memory range check, which is
worse than that of Linux 2.3.xx, so avoid the last 64kb. */
addr_max -= 0x10000;
addr_min = GRUB_LINUX_BZIMAGE_ADDR + grub_linux16_prot_size;
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (relocator, &ch,
addr_min, addr_max - size,
size, 0x1000,
GRUB_RELOCATOR_PREFERENCE_HIGH, 0);
if (err)
return err;
initrd_chunk = get_virtual_current_address (ch);
initrd_addr = get_physical_target_address (ch);
}
if (grub_initrd_load (&initrd_ctx, argv, initrd_chunk))
goto fail;
lh->ramdisk_image = initrd_addr;
lh->ramdisk_size = size;
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
static grub_command_t cmd_linux, cmd_initrd;
GRUB_MOD_INIT(linux16)
{
cmd_linux =
grub_register_command ("linux16", grub_cmd_linux,
0, N_("Load Linux."));
cmd_initrd =
grub_register_command ("initrd16", grub_cmd_initrd,
0, N_("Load initrd."));
my_mod = mod;
}
GRUB_MOD_FINI(linux16)
{
grub_unregister_command (cmd_linux);
grub_unregister_command (cmd_initrd);
}

View File

@@ -0,0 +1,978 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2013 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/loader.h>
#include <grub/memory.h>
#include <grub/normal.h>
#include <grub/file.h>
#include <grub/disk.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/types.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/term.h>
#include <grub/cpu/linux.h>
#include <grub/video.h>
#include <grub/video_fb.h>
#include <grub/command.h>
#include <grub/xen/relocator.h>
#include <grub/i18n.h>
#include <grub/elf.h>
#include <grub/elfload.h>
#include <grub/lib/cmdline.h>
#include <grub/xen.h>
#include <grub/xen_file.h>
#include <grub/linux.h>
#include <grub/i386/memory.h>
#include <grub/verify.h>
GRUB_MOD_LICENSE ("GPLv3+");
#ifdef __x86_64__
#define NUMBER_OF_LEVELS 4
#define INTERMEDIATE_OR (GRUB_PAGE_PRESENT | GRUB_PAGE_RW | GRUB_PAGE_USER)
#define VIRT_MASK 0x0000ffffffffffffULL
#else
#define NUMBER_OF_LEVELS 3
#define INTERMEDIATE_OR (GRUB_PAGE_PRESENT | GRUB_PAGE_RW)
#define VIRT_MASK 0x00000000ffffffffULL
#define HYPERVISOR_PUD_ADDRESS 0xc0000000ULL
#endif
struct grub_xen_mapping_lvl {
grub_uint64_t virt_start;
grub_uint64_t virt_end;
grub_uint64_t pfn_start;
grub_uint64_t n_pt_pages;
};
struct grub_xen_mapping {
grub_uint64_t *where;
struct grub_xen_mapping_lvl area;
struct grub_xen_mapping_lvl lvls[NUMBER_OF_LEVELS];
};
struct xen_loader_state {
struct grub_relocator *relocator;
struct grub_relocator_xen_state state;
struct start_info next_start;
struct grub_xen_file_info xen_inf;
grub_xen_mfn_t *virt_mfn_list;
struct start_info *virt_start_info;
grub_xen_mfn_t console_pfn;
grub_uint64_t max_addr;
grub_uint64_t pgtbl_end;
struct xen_multiboot_mod_list *module_info_page;
grub_uint64_t modules_target_start;
grub_size_t n_modules;
struct grub_xen_mapping *map_reloc;
struct grub_xen_mapping mappings[XEN_MAX_MAPPINGS];
int n_mappings;
int loaded;
};
static struct xen_loader_state xen_state;
static grub_dl_t my_mod;
#define PAGE_SIZE (1UL << PAGE_SHIFT)
#define MAX_MODULES (PAGE_SIZE / sizeof (struct xen_multiboot_mod_list))
#define STACK_SIZE 1048576
#define ADDITIONAL_SIZE (1 << 19)
#define ALIGN_SIZE (1 << 22)
#define LOG_POINTERS_PER_PAGE 9
#define POINTERS_PER_PAGE (1 << LOG_POINTERS_PER_PAGE)
static grub_uint64_t
page2offset (grub_uint64_t page)
{
return page << PAGE_SHIFT;
}
static grub_err_t
get_pgtable_size (grub_uint64_t from, grub_uint64_t to, grub_uint64_t pfn)
{
struct grub_xen_mapping *map, *map_cmp;
grub_uint64_t mask, bits;
int i, m;
if (xen_state.n_mappings == XEN_MAX_MAPPINGS)
return grub_error (GRUB_ERR_BUG, "too many mapped areas");
grub_dprintf ("xen", "get_pgtable_size %d from=%llx, to=%llx, pfn=%llx\n",
xen_state.n_mappings, (unsigned long long) from,
(unsigned long long) to, (unsigned long long) pfn);
map = xen_state.mappings + xen_state.n_mappings;
grub_memset (map, 0, sizeof (*map));
map->area.virt_start = from & VIRT_MASK;
map->area.virt_end = (to - 1) & VIRT_MASK;
map->area.n_pt_pages = 0;
for (i = NUMBER_OF_LEVELS - 1; i >= 0; i--)
{
map->lvls[i].pfn_start = pfn + map->area.n_pt_pages;
if (i == NUMBER_OF_LEVELS - 1)
{
if (xen_state.n_mappings == 0)
{
map->lvls[i].virt_start = 0;
map->lvls[i].virt_end = VIRT_MASK;
map->lvls[i].n_pt_pages = 1;
map->area.n_pt_pages++;
}
continue;
}
bits = PAGE_SHIFT + (i + 1) * LOG_POINTERS_PER_PAGE;
mask = (1ULL << bits) - 1;
map->lvls[i].virt_start = map->area.virt_start & ~mask;
map->lvls[i].virt_end = map->area.virt_end | mask;
#ifdef __i386__
/* PAE wants last root directory present. */
if (i == 1 && to <= HYPERVISOR_PUD_ADDRESS && xen_state.n_mappings == 0)
map->lvls[i].virt_end = VIRT_MASK;
#endif
for (m = 0; m < xen_state.n_mappings; m++)
{
map_cmp = xen_state.mappings + m;
if (map_cmp->lvls[i].virt_start == map_cmp->lvls[i].virt_end)
continue;
if (map->lvls[i].virt_start >= map_cmp->lvls[i].virt_start &&
map->lvls[i].virt_end <= map_cmp->lvls[i].virt_end)
{
map->lvls[i].virt_start = 0;
map->lvls[i].virt_end = 0;
break;
}
if (map->lvls[i].virt_start >= map_cmp->lvls[i].virt_start &&
map->lvls[i].virt_start <= map_cmp->lvls[i].virt_end)
map->lvls[i].virt_start = map_cmp->lvls[i].virt_end + 1;
if (map->lvls[i].virt_end >= map_cmp->lvls[i].virt_start &&
map->lvls[i].virt_end <= map_cmp->lvls[i].virt_end)
map->lvls[i].virt_end = map_cmp->lvls[i].virt_start - 1;
}
if (map->lvls[i].virt_start < map->lvls[i].virt_end)
map->lvls[i].n_pt_pages =
((map->lvls[i].virt_end - map->lvls[i].virt_start) >> bits) + 1;
map->area.n_pt_pages += map->lvls[i].n_pt_pages;
grub_dprintf ("xen", "get_pgtable_size level %d: virt %llx-%llx %d pts\n",
i, (unsigned long long) map->lvls[i].virt_start,
(unsigned long long) map->lvls[i].virt_end,
(int) map->lvls[i].n_pt_pages);
}
grub_dprintf ("xen", "get_pgtable_size return: %d page tables\n",
(int) map->area.n_pt_pages);
xen_state.state.paging_start[xen_state.n_mappings] = pfn;
xen_state.state.paging_size[xen_state.n_mappings] = map->area.n_pt_pages;
return GRUB_ERR_NONE;
}
static grub_uint64_t *
get_pg_table_virt (int mapping, int level)
{
grub_uint64_t pfn;
struct grub_xen_mapping *map;
map = xen_state.mappings + mapping;
pfn = map->lvls[level].pfn_start - map->lvls[NUMBER_OF_LEVELS - 1].pfn_start;
return map->where + pfn * POINTERS_PER_PAGE;
}
static grub_uint64_t
get_pg_table_prot (int level, grub_uint64_t pfn)
{
int m;
grub_uint64_t pfn_s, pfn_e;
if (level > 0)
return INTERMEDIATE_OR;
for (m = 0; m < xen_state.n_mappings; m++)
{
pfn_s = xen_state.mappings[m].lvls[NUMBER_OF_LEVELS - 1].pfn_start;
pfn_e = xen_state.mappings[m].area.n_pt_pages + pfn_s;
if (pfn >= pfn_s && pfn < pfn_e)
return GRUB_PAGE_PRESENT | GRUB_PAGE_USER;
}
return GRUB_PAGE_PRESENT | GRUB_PAGE_RW | GRUB_PAGE_USER;
}
static void
generate_page_table (grub_xen_mfn_t *mfn_list)
{
int l, m1, m2;
long p, p_s, p_e;
grub_uint64_t start, end, pfn;
grub_uint64_t *pg;
struct grub_xen_mapping_lvl *lvl;
for (m1 = 0; m1 < xen_state.n_mappings; m1++)
grub_memset (xen_state.mappings[m1].where, 0,
xen_state.mappings[m1].area.n_pt_pages * PAGE_SIZE);
for (l = NUMBER_OF_LEVELS - 1; l >= 0; l--)
{
for (m1 = 0; m1 < xen_state.n_mappings; m1++)
{
start = xen_state.mappings[m1].lvls[l].virt_start;
end = xen_state.mappings[m1].lvls[l].virt_end;
pg = get_pg_table_virt(m1, l);
for (m2 = 0; m2 < xen_state.n_mappings; m2++)
{
lvl = (l > 0) ? xen_state.mappings[m2].lvls + l - 1
: &xen_state.mappings[m2].area;
if (l > 0 && lvl->n_pt_pages == 0)
continue;
if (lvl->virt_start >= end || lvl->virt_end <= start)
continue;
p_s = (grub_max (start, lvl->virt_start) - start) >>
(PAGE_SHIFT + l * LOG_POINTERS_PER_PAGE);
p_e = (grub_min (end, lvl->virt_end) - start) >>
(PAGE_SHIFT + l * LOG_POINTERS_PER_PAGE);
pfn = ((grub_max (start, lvl->virt_start) - lvl->virt_start) >>
(PAGE_SHIFT + l * LOG_POINTERS_PER_PAGE)) + lvl->pfn_start;
grub_dprintf ("xen", "write page table entries level %d pg %p "
"mapping %d/%d index %lx-%lx pfn %llx\n",
l, pg, m1, m2, p_s, p_e, (unsigned long long) pfn);
for (p = p_s; p <= p_e; p++)
{
pg[p] = page2offset (mfn_list[pfn]) |
get_pg_table_prot (l, pfn);
pfn++;
}
}
}
}
}
static grub_err_t
set_mfns (grub_xen_mfn_t pfn)
{
grub_xen_mfn_t i, t;
grub_xen_mfn_t cn_pfn = -1, st_pfn = -1;
struct mmu_update m2p_updates[4];
for (i = 0; i < grub_xen_start_page_addr->nr_pages; i++)
{
if (xen_state.virt_mfn_list[i] ==
grub_xen_start_page_addr->console.domU.mfn)
cn_pfn = i;
if (xen_state.virt_mfn_list[i] == grub_xen_start_page_addr->store_mfn)
st_pfn = i;
}
if (cn_pfn == (grub_xen_mfn_t)-1)
return grub_error (GRUB_ERR_BUG, "no console");
if (st_pfn == (grub_xen_mfn_t)-1)
return grub_error (GRUB_ERR_BUG, "no store");
t = xen_state.virt_mfn_list[pfn];
xen_state.virt_mfn_list[pfn] = xen_state.virt_mfn_list[cn_pfn];
xen_state.virt_mfn_list[cn_pfn] = t;
t = xen_state.virt_mfn_list[pfn + 1];
xen_state.virt_mfn_list[pfn + 1] = xen_state.virt_mfn_list[st_pfn];
xen_state.virt_mfn_list[st_pfn] = t;
m2p_updates[0].ptr =
page2offset (xen_state.virt_mfn_list[pfn]) | MMU_MACHPHYS_UPDATE;
m2p_updates[0].val = pfn;
m2p_updates[1].ptr =
page2offset (xen_state.virt_mfn_list[pfn + 1]) | MMU_MACHPHYS_UPDATE;
m2p_updates[1].val = pfn + 1;
m2p_updates[2].ptr =
page2offset (xen_state.virt_mfn_list[cn_pfn]) | MMU_MACHPHYS_UPDATE;
m2p_updates[2].val = cn_pfn;
m2p_updates[3].ptr =
page2offset (xen_state.virt_mfn_list[st_pfn]) | MMU_MACHPHYS_UPDATE;
m2p_updates[3].val = st_pfn;
grub_xen_mmu_update (m2p_updates, 4, NULL, DOMID_SELF);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_xen_p2m_alloc (void)
{
grub_relocator_chunk_t ch;
grub_size_t p2msize, p2malloc;
grub_err_t err;
struct grub_xen_mapping *map;
if (xen_state.virt_mfn_list)
return GRUB_ERR_NONE;
map = xen_state.mappings + xen_state.n_mappings;
p2msize = ALIGN_UP (sizeof (grub_xen_mfn_t) *
grub_xen_start_page_addr->nr_pages, PAGE_SIZE);
if (xen_state.xen_inf.has_p2m_base)
{
err = get_pgtable_size (xen_state.xen_inf.p2m_base,
xen_state.xen_inf.p2m_base + p2msize,
(xen_state.max_addr + p2msize) >> PAGE_SHIFT);
if (err)
return err;
map->area.pfn_start = xen_state.max_addr >> PAGE_SHIFT;
p2malloc = p2msize + page2offset (map->area.n_pt_pages);
xen_state.n_mappings++;
xen_state.next_start.mfn_list = xen_state.xen_inf.p2m_base;
xen_state.next_start.first_p2m_pfn = map->area.pfn_start;
xen_state.next_start.nr_p2m_frames = p2malloc >> PAGE_SHIFT;
}
else
{
xen_state.next_start.mfn_list =
xen_state.max_addr + xen_state.xen_inf.virt_base;
p2malloc = p2msize;
}
xen_state.state.mfn_list = xen_state.max_addr;
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr, p2malloc);
if (err)
return err;
xen_state.virt_mfn_list = get_virtual_current_address (ch);
if (xen_state.xen_inf.has_p2m_base)
map->where = (grub_uint64_t *) xen_state.virt_mfn_list +
p2msize / sizeof (grub_uint64_t);
grub_memcpy (xen_state.virt_mfn_list,
(void *) grub_xen_start_page_addr->mfn_list, p2msize);
xen_state.max_addr += p2malloc;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_xen_special_alloc (void)
{
grub_relocator_chunk_t ch;
grub_err_t err;
if (xen_state.virt_start_info)
return GRUB_ERR_NONE;
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr,
sizeof (xen_state.next_start));
if (err)
return err;
xen_state.state.start_info = xen_state.max_addr + xen_state.xen_inf.virt_base;
xen_state.virt_start_info = get_virtual_current_address (ch);
xen_state.max_addr =
ALIGN_UP (xen_state.max_addr + sizeof (xen_state.next_start), PAGE_SIZE);
xen_state.console_pfn = xen_state.max_addr >> PAGE_SHIFT;
xen_state.max_addr += 2 * PAGE_SIZE;
xen_state.next_start.nr_pages = grub_xen_start_page_addr->nr_pages;
grub_memcpy (xen_state.next_start.magic, grub_xen_start_page_addr->magic,
sizeof (xen_state.next_start.magic));
xen_state.next_start.store_mfn = grub_xen_start_page_addr->store_mfn;
xen_state.next_start.store_evtchn = grub_xen_start_page_addr->store_evtchn;
xen_state.next_start.console.domU = grub_xen_start_page_addr->console.domU;
xen_state.next_start.shared_info = grub_xen_start_page_addr->shared_info;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_xen_pt_alloc (void)
{
grub_relocator_chunk_t ch;
grub_err_t err;
grub_uint64_t nr_info_pages;
grub_uint64_t nr_need_pages;
grub_uint64_t try_virt_end;
struct grub_xen_mapping *map;
if (xen_state.pgtbl_end)
return GRUB_ERR_NONE;
map = xen_state.mappings + xen_state.n_mappings;
xen_state.map_reloc = map + 1;
xen_state.next_start.pt_base =
xen_state.max_addr + xen_state.xen_inf.virt_base;
nr_info_pages = xen_state.max_addr >> PAGE_SHIFT;
nr_need_pages = nr_info_pages;
while (1)
{
try_virt_end = ALIGN_UP (xen_state.xen_inf.virt_base +
page2offset (nr_need_pages) +
ADDITIONAL_SIZE + STACK_SIZE, ALIGN_SIZE);
err = get_pgtable_size (xen_state.xen_inf.virt_base, try_virt_end,
nr_info_pages);
if (err)
return err;
xen_state.n_mappings++;
/* Map the relocator page either at virtual 0 or after end of area. */
nr_need_pages = nr_info_pages + map->area.n_pt_pages;
if (xen_state.xen_inf.virt_base)
err = get_pgtable_size (0, PAGE_SIZE, nr_need_pages);
else
err = get_pgtable_size (try_virt_end, try_virt_end + PAGE_SIZE,
nr_need_pages);
if (err)
return err;
nr_need_pages += xen_state.map_reloc->area.n_pt_pages;
if (xen_state.xen_inf.virt_base + page2offset (nr_need_pages) <=
try_virt_end)
break;
xen_state.n_mappings--;
}
xen_state.n_mappings++;
nr_need_pages = map->area.n_pt_pages + xen_state.map_reloc->area.n_pt_pages;
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr,
page2offset (nr_need_pages));
if (err)
return err;
map->where = get_virtual_current_address (ch);
map->area.pfn_start = 0;
xen_state.max_addr += page2offset (nr_need_pages);
xen_state.state.stack =
xen_state.max_addr + STACK_SIZE + xen_state.xen_inf.virt_base;
xen_state.next_start.nr_pt_frames = nr_need_pages;
xen_state.max_addr = try_virt_end - xen_state.xen_inf.virt_base;
xen_state.pgtbl_end = xen_state.max_addr >> PAGE_SHIFT;
xen_state.map_reloc->where = (grub_uint64_t *) ((char *) map->where +
page2offset (map->area.n_pt_pages));
return GRUB_ERR_NONE;
}
/* Allocate all not yet allocated areas mapped by initial page tables. */
static grub_err_t
grub_xen_alloc_boot_data (void)
{
grub_err_t err;
if (!xen_state.xen_inf.has_p2m_base)
{
err = grub_xen_p2m_alloc ();
if (err)
return err;
}
err = grub_xen_special_alloc ();
if (err)
return err;
err = grub_xen_pt_alloc ();
if (err)
return err;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_xen_boot (void)
{
grub_err_t err;
grub_uint64_t nr_pages;
struct gnttab_set_version gnttab_setver;
grub_size_t i;
if (grub_xen_n_allocated_shared_pages)
return grub_error (GRUB_ERR_BUG, "active grants");
err = grub_xen_alloc_boot_data ();
if (err)
return err;
if (xen_state.xen_inf.has_p2m_base)
{
err = grub_xen_p2m_alloc ();
if (err)
return err;
}
err = set_mfns (xen_state.console_pfn);
if (err)
return err;
nr_pages = xen_state.max_addr >> PAGE_SHIFT;
grub_dprintf ("xen", "bootstrap domain %llx+%llx\n",
(unsigned long long) xen_state.xen_inf.virt_base,
(unsigned long long) page2offset (nr_pages));
xen_state.map_reloc->area.pfn_start = nr_pages;
generate_page_table (xen_state.virt_mfn_list);
xen_state.state.entry_point = xen_state.xen_inf.entry_point;
*xen_state.virt_start_info = xen_state.next_start;
grub_memset (&gnttab_setver, 0, sizeof (gnttab_setver));
gnttab_setver.version = 1;
grub_xen_grant_table_op (GNTTABOP_set_version, &gnttab_setver, 1);
for (i = 0; i < ARRAY_SIZE (grub_xen_shared_info->evtchn_pending); i++)
grub_xen_shared_info->evtchn_pending[i] = 0;
return grub_relocator_xen_boot (xen_state.relocator, xen_state.state, nr_pages,
xen_state.xen_inf.virt_base <
PAGE_SIZE ? page2offset (nr_pages) : 0,
xen_state.pgtbl_end - 1,
page2offset (xen_state.pgtbl_end - 1) +
xen_state.xen_inf.virt_base);
}
static void
grub_xen_reset (void)
{
grub_relocator_unload (xen_state.relocator);
grub_memset (&xen_state, 0, sizeof (xen_state));
}
static grub_err_t
grub_xen_unload (void)
{
grub_xen_reset ();
grub_dl_unref (my_mod);
return GRUB_ERR_NONE;
}
#define HYPERCALL_INTERFACE_SIZE 32
#ifdef __x86_64__
static grub_uint8_t template[] =
{
0x51, /* push %rcx */
0x41, 0x53, /* push %r11 */
0x48, 0xc7, 0xc0, 0xbb, 0xaa, 0x00, 0x00, /* mov $0xaabb,%rax */
0x0f, 0x05, /* syscall */
0x41, 0x5b, /* pop %r11 */
0x59, /* pop %rcx */
0xc3 /* ret */
};
static grub_uint8_t template_iret[] =
{
0x51, /* push %rcx */
0x41, 0x53, /* push %r11 */
0x50, /* push %rax */
0x48, 0xc7, 0xc0, 0x17, 0x00, 0x00, 0x00, /* mov $0x17,%rax */
0x0f, 0x05 /* syscall */
};
#define CALLNO_OFFSET 6
#else
static grub_uint8_t template[] =
{
0xb8, 0xbb, 0xaa, 0x00, 0x00, /* mov imm32, %eax */
0xcd, 0x82, /* int $0x82 */
0xc3 /* ret */
};
static grub_uint8_t template_iret[] =
{
0x50, /* push %eax */
0xb8, 0x17, 0x00, 0x00, 0x00, /* mov $0x17,%eax */
0xcd, 0x82, /* int $0x82 */
};
#define CALLNO_OFFSET 1
#endif
static void
set_hypercall_interface (grub_uint8_t *tgt, unsigned callno)
{
if (callno == 0x17)
{
grub_memcpy (tgt, template_iret, ARRAY_SIZE (template_iret));
grub_memset (tgt + ARRAY_SIZE (template_iret), 0xcc,
HYPERCALL_INTERFACE_SIZE - ARRAY_SIZE (template_iret));
return;
}
grub_memcpy (tgt, template, ARRAY_SIZE (template));
grub_memset (tgt + ARRAY_SIZE (template), 0xcc,
HYPERCALL_INTERFACE_SIZE - ARRAY_SIZE (template));
tgt[CALLNO_OFFSET] = callno & 0xff;
tgt[CALLNO_OFFSET + 1] = callno >> 8;
}
#ifdef __x86_64__
#define grub_elfXX_load grub_elf64_load
#else
#define grub_elfXX_load grub_elf32_load
#endif
static grub_err_t
grub_cmd_xen (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t file;
grub_elf_t elf;
grub_err_t err;
void *kern_chunk_src;
grub_relocator_chunk_t ch;
grub_addr_t kern_start;
grub_addr_t kern_end;
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
/* Call grub_loader_unset early to avoid it being called by grub_loader_set */
grub_loader_unset ();
grub_xen_reset ();
err = grub_create_loader_cmdline (argc - 1, argv + 1,
(char *) xen_state.next_start.cmd_line,
sizeof (xen_state.next_start.cmd_line) - 1,
GRUB_VERIFY_KERNEL_CMDLINE);
if (err)
return err;
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (!file)
return grub_errno;
elf = grub_xen_file (file);
if (!elf)
goto fail;
err = grub_xen_get_info (elf, &xen_state.xen_inf);
if (err)
goto fail;
#ifdef __x86_64__
if (xen_state.xen_inf.arch != GRUB_XEN_FILE_X86_64)
#else
if (xen_state.xen_inf.arch != GRUB_XEN_FILE_I386_PAE
&& xen_state.xen_inf.arch != GRUB_XEN_FILE_I386_PAE_BIMODE)
#endif
{
grub_error (GRUB_ERR_BAD_OS, "incompatible architecture: %d",
xen_state.xen_inf.arch);
goto fail;
}
if (xen_state.xen_inf.virt_base & (PAGE_SIZE - 1))
{
grub_error (GRUB_ERR_BAD_OS, "unaligned virt_base");
goto fail;
}
grub_dprintf ("xen", "virt_base = %llx, entry = %llx\n",
(unsigned long long) xen_state.xen_inf.virt_base,
(unsigned long long) xen_state.xen_inf.entry_point);
xen_state.relocator = grub_relocator_new ();
if (!xen_state.relocator)
goto fail;
kern_start = xen_state.xen_inf.kern_start - xen_state.xen_inf.paddr_offset;
kern_end = xen_state.xen_inf.kern_end - xen_state.xen_inf.paddr_offset;
if (xen_state.xen_inf.has_hypercall_page)
{
grub_dprintf ("xen", "hypercall page at 0x%llx\n",
(unsigned long long) xen_state.xen_inf.hypercall_page);
kern_start = grub_min (kern_start, xen_state.xen_inf.hypercall_page -
xen_state.xen_inf.virt_base);
kern_end = grub_max (kern_end, xen_state.xen_inf.hypercall_page -
xen_state.xen_inf.virt_base + PAGE_SIZE);
}
xen_state.max_addr = ALIGN_UP (kern_end, PAGE_SIZE);
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch, kern_start,
kern_end - kern_start);
if (err)
goto fail;
kern_chunk_src = get_virtual_current_address (ch);
grub_dprintf ("xen", "paddr_offset = 0x%llx\n",
(unsigned long long) xen_state.xen_inf.paddr_offset);
grub_dprintf ("xen", "kern_start = 0x%llx, kern_end = 0x%llx\n",
(unsigned long long) xen_state.xen_inf.kern_start,
(unsigned long long) xen_state.xen_inf.kern_end);
err = grub_elfXX_load (elf, argv[0],
(grub_uint8_t *) kern_chunk_src - kern_start
- xen_state.xen_inf.paddr_offset, 0, 0, 0);
if (xen_state.xen_inf.has_hypercall_page)
{
unsigned i;
for (i = 0; i < PAGE_SIZE / HYPERCALL_INTERFACE_SIZE; i++)
set_hypercall_interface ((grub_uint8_t *) kern_chunk_src +
i * HYPERCALL_INTERFACE_SIZE +
xen_state.xen_inf.hypercall_page -
xen_state.xen_inf.virt_base - kern_start, i);
}
if (err)
goto fail;
grub_dl_ref (my_mod);
xen_state.loaded = 1;
grub_loader_set (grub_xen_boot, grub_xen_unload, 0);
goto fail;
fail:
/* grub_errno might be clobbered by further calls, save the error reason. */
err = grub_errno;
if (elf)
grub_elf_close (elf);
else if (file)
grub_file_close (file);
if (err != GRUB_ERR_NONE)
grub_xen_reset ();
return err;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_size_t size = 0;
grub_err_t err;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
grub_relocator_chunk_t ch;
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
if (!xen_state.loaded)
{
grub_error (GRUB_ERR_BAD_ARGUMENT,
N_("you need to load the kernel first"));
goto fail;
}
if (xen_state.next_start.mod_start || xen_state.next_start.mod_len)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("initrd already loaded"));
goto fail;
}
if (xen_state.xen_inf.unmapped_initrd)
{
err = grub_xen_alloc_boot_data ();
if (err)
goto fail;
}
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
if (size)
{
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr, size);
if (err)
goto fail;
if (grub_initrd_load (&initrd_ctx, argv,
get_virtual_current_address (ch)))
goto fail;
}
xen_state.next_start.mod_len = size;
if (xen_state.xen_inf.unmapped_initrd)
{
xen_state.next_start.flags |= SIF_MOD_START_PFN;
xen_state.next_start.mod_start = xen_state.max_addr >> PAGE_SHIFT;
}
else
xen_state.next_start.mod_start =
xen_state.max_addr + xen_state.xen_inf.virt_base;
grub_dprintf ("xen", "Initrd, addr=0x%x, size=0x%x\n",
(unsigned) (xen_state.max_addr + xen_state.xen_inf.virt_base),
(unsigned) size);
xen_state.max_addr = ALIGN_UP (xen_state.max_addr + size, PAGE_SIZE);
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
static grub_err_t
grub_cmd_module (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_size_t size = 0;
grub_err_t err;
grub_relocator_chunk_t ch;
grub_size_t cmdline_len;
int nounzip = 0;
grub_file_t file;
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (grub_strcmp (argv[0], "--nounzip") == 0)
{
argv++;
argc--;
nounzip = 1;
}
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (!xen_state.loaded)
{
return grub_error (GRUB_ERR_BAD_ARGUMENT,
N_("you need to load the kernel first"));
}
if ((xen_state.next_start.mod_start || xen_state.next_start.mod_len) &&
!xen_state.module_info_page)
{
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("initrd already loaded"));
}
/* Leave one space for terminator. */
if (xen_state.n_modules >= MAX_MODULES - 1)
{
return grub_error (GRUB_ERR_BAD_ARGUMENT, "too many modules");
}
if (!xen_state.module_info_page)
{
xen_state.xen_inf.unmapped_initrd = 0;
xen_state.n_modules = 0;
xen_state.max_addr = ALIGN_UP (xen_state.max_addr, PAGE_SIZE);
xen_state.modules_target_start = xen_state.max_addr;
xen_state.next_start.mod_start =
xen_state.max_addr + xen_state.xen_inf.virt_base;
xen_state.next_start.flags |= SIF_MULTIBOOT_MOD;
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr, MAX_MODULES
*
sizeof (xen_state.module_info_page
[0]));
if (err)
return err;
xen_state.module_info_page = get_virtual_current_address (ch);
grub_memset (xen_state.module_info_page, 0, MAX_MODULES
* sizeof (xen_state.module_info_page[0]));
xen_state.max_addr +=
MAX_MODULES * sizeof (xen_state.module_info_page[0]);
}
xen_state.max_addr = ALIGN_UP (xen_state.max_addr, PAGE_SIZE);
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_INITRD |
(nounzip ? GRUB_FILE_TYPE_NO_DECOMPRESS : GRUB_FILE_TYPE_NONE));
if (!file)
return grub_errno;
size = grub_file_size (file);
cmdline_len = grub_loader_cmdline_size (argc - 1, argv + 1);
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr, cmdline_len);
if (err)
goto fail;
err = grub_create_loader_cmdline (argc - 1, argv + 1,
get_virtual_current_address (ch), cmdline_len,
GRUB_VERIFY_MODULE_CMDLINE);
if (err)
goto fail;
xen_state.module_info_page[xen_state.n_modules].cmdline =
xen_state.max_addr - xen_state.modules_target_start;
xen_state.max_addr = ALIGN_UP (xen_state.max_addr + cmdline_len, PAGE_SIZE);
if (size)
{
err = grub_relocator_alloc_chunk_addr (xen_state.relocator, &ch,
xen_state.max_addr, size);
if (err)
goto fail;
if (grub_file_read (file, get_virtual_current_address (ch), size)
!= (grub_ssize_t) size)
{
if (!grub_errno)
grub_error (GRUB_ERR_FILE_READ_ERROR,
N_("premature end of file %s"), argv[0]);
goto fail;
}
}
xen_state.next_start.mod_len =
xen_state.max_addr + size - xen_state.modules_target_start;
xen_state.module_info_page[xen_state.n_modules].mod_start =
xen_state.max_addr - xen_state.modules_target_start;
xen_state.module_info_page[xen_state.n_modules].mod_end =
xen_state.max_addr + size - xen_state.modules_target_start;
xen_state.n_modules++;
grub_dprintf ("xen", "module, addr=0x%x, size=0x%x\n",
(unsigned) xen_state.max_addr, (unsigned) size);
xen_state.max_addr = ALIGN_UP (xen_state.max_addr + size, PAGE_SIZE);
fail:
grub_file_close (file);
return grub_errno;
}
static grub_command_t cmd_xen, cmd_initrd, cmd_module, cmd_multiboot;
GRUB_MOD_INIT (xen)
{
cmd_xen = grub_register_command ("linux", grub_cmd_xen,
0, N_("Load Linux."));
cmd_multiboot = grub_register_command ("multiboot", grub_cmd_xen,
0, N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
0, N_("Load initrd."));
cmd_module = grub_register_command ("module", grub_cmd_module,
0, N_("Load module."));
my_mod = mod;
}
GRUB_MOD_FINI (xen)
{
grub_unregister_command (cmd_xen);
grub_unregister_command (cmd_initrd);
grub_unregister_command (cmd_multiboot);
grub_unregister_command (cmd_module);
}

View File

@@ -0,0 +1,607 @@
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2008,2010 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/loader.h>
#include <grub/file.h>
#include <grub/disk.h>
#include <grub/err.h>
#include <grub/misc.h>
#include <grub/types.h>
#include <grub/command.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/cache.h>
#include <grub/kernel.h>
#include <grub/efi/api.h>
#include <grub/efi/efi.h>
#include <grub/elf.h>
#include <grub/i18n.h>
#include <grub/env.h>
#include <grub/linux.h>
#include <grub/verify.h>
GRUB_MOD_LICENSE ("GPLv3+");
#pragma GCC diagnostic ignored "-Wcast-align"
#define ALIGN_MIN (256*1024*1024)
#define GRUB_ELF_SEARCH 1024
#define BOOT_PARAM_SIZE 16384
struct ia64_boot_param
{
grub_uint64_t command_line; /* physical address of command line. */
grub_uint64_t efi_systab; /* physical address of EFI system table */
grub_uint64_t efi_memmap; /* physical address of EFI memory map */
grub_uint64_t efi_memmap_size; /* size of EFI memory map */
grub_uint64_t efi_memdesc_size; /* size of an EFI memory map descriptor */
grub_uint32_t efi_memdesc_version; /* memory descriptor version */
struct
{
grub_uint16_t num_cols; /* number of columns on console output dev */
grub_uint16_t num_rows; /* number of rows on console output device */
grub_uint16_t orig_x; /* cursor's x position */
grub_uint16_t orig_y; /* cursor's y position */
} console_info;
grub_uint64_t fpswa; /* physical address of the fpswa interface */
grub_uint64_t initrd_start;
grub_uint64_t initrd_size;
};
typedef struct
{
grub_uint32_t revision;
grub_uint32_t reserved;
void *fpswa;
} fpswa_interface_t;
static fpswa_interface_t *fpswa;
#define NEXT_MEMORY_DESCRIPTOR(desc, size) \
((grub_efi_memory_descriptor_t *) ((char *) (desc) + (size)))
static grub_dl_t my_mod;
static int loaded;
/* Kernel base and size. */
static void *kernel_mem;
static grub_efi_uintn_t kernel_pages;
static grub_uint64_t entry;
/* Initrd base and size. */
static void *initrd_mem;
static grub_efi_uintn_t initrd_pages;
static grub_efi_uintn_t initrd_size;
static struct ia64_boot_param *boot_param;
static grub_efi_uintn_t boot_param_pages;
static inline grub_size_t
page_align (grub_size_t size)
{
return (size + (1 << 12) - 1) & (~((1 << 12) - 1));
}
static void
query_fpswa (void)
{
grub_efi_handle_t fpswa_image;
grub_efi_boot_services_t *bs;
grub_efi_status_t status;
grub_efi_uintn_t size;
static const grub_efi_guid_t fpswa_protocol =
{ 0xc41b6531, 0x97b9, 0x11d3,
{0x9a, 0x29, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d} };
if (fpswa != NULL)
return;
size = sizeof(grub_efi_handle_t);
bs = grub_efi_system_table->boot_services;
status = bs->locate_handle (GRUB_EFI_BY_PROTOCOL,
(void *) &fpswa_protocol,
NULL, &size, &fpswa_image);
if (status != GRUB_EFI_SUCCESS)
{
grub_printf ("%s\n", _("Could not locate FPSWA driver"));
return;
}
status = bs->handle_protocol (fpswa_image,
(void *) &fpswa_protocol, (void *) &fpswa);
if (status != GRUB_EFI_SUCCESS)
{
grub_printf ("%s\n",
_("FPSWA protocol wasn't able to find the interface"));
return;
}
}
static void
free_pages (void)
{
if (kernel_mem)
{
grub_efi_free_pages ((grub_addr_t) kernel_mem, kernel_pages);
kernel_mem = 0;
}
if (initrd_mem)
{
grub_efi_free_pages ((grub_addr_t) initrd_mem, initrd_pages);
initrd_mem = 0;
}
if (boot_param)
{
/* Free bootparam. */
grub_efi_free_pages ((grub_efi_physical_address_t) boot_param,
boot_param_pages);
boot_param = 0;
}
}
static void *
allocate_pages (grub_uint64_t align, grub_uint64_t size_pages,
grub_uint64_t nobase)
{
grub_uint64_t size;
grub_efi_uintn_t desc_size;
grub_efi_memory_descriptor_t *mmap, *mmap_end;
grub_efi_uintn_t mmap_size, tmp_mmap_size;
grub_efi_memory_descriptor_t *desc;
void *mem = NULL;
size = size_pages << 12;
mmap_size = grub_efi_find_mmap_size ();
if (!mmap_size)
return 0;
/* Read the memory map temporarily, to find free space. */
mmap = grub_malloc (mmap_size);
if (! mmap)
return 0;
tmp_mmap_size = mmap_size;
if (grub_efi_get_memory_map (&tmp_mmap_size, mmap, 0, &desc_size, 0) <= 0)
{
grub_error (GRUB_ERR_IO, "cannot get memory map");
goto fail;
}
mmap_end = NEXT_MEMORY_DESCRIPTOR (mmap, tmp_mmap_size);
/* First, find free pages for the real mode code
and the memory map buffer. */
for (desc = mmap;
desc < mmap_end;
desc = NEXT_MEMORY_DESCRIPTOR (desc, desc_size))
{
grub_uint64_t start, end;
grub_uint64_t aligned_start;
if (desc->type != GRUB_EFI_CONVENTIONAL_MEMORY)
continue;
start = desc->physical_start;
end = start + (desc->num_pages << 12);
/* Align is a power of 2. */
aligned_start = (start + align - 1) & ~(align - 1);
if (aligned_start + size > end)
continue;
if (aligned_start == nobase)
aligned_start += align;
if (aligned_start + size > end)
continue;
mem = grub_efi_allocate_fixed (aligned_start, size_pages);
if (! mem)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate memory");
goto fail;
}
break;
}
if (! mem)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate memory");
goto fail;
}
grub_free (mmap);
return mem;
fail:
grub_free (mmap);
free_pages ();
return 0;
}
static void
set_boot_param_console (void)
{
grub_efi_simple_text_output_interface_t *conout;
grub_efi_uintn_t cols, rows;
conout = grub_efi_system_table->con_out;
if (conout->query_mode (conout, conout->mode->mode, &cols, &rows)
!= GRUB_EFI_SUCCESS)
return;
grub_dprintf ("linux",
"Console info: cols=%lu rows=%lu x=%u y=%u\n",
cols, rows,
conout->mode->cursor_column, conout->mode->cursor_row);
boot_param->console_info.num_cols = cols;
boot_param->console_info.num_rows = rows;
boot_param->console_info.orig_x = conout->mode->cursor_column;
boot_param->console_info.orig_y = conout->mode->cursor_row;
}
static grub_err_t
grub_linux_boot (void)
{
grub_efi_uintn_t mmap_size;
grub_efi_uintn_t map_key;
grub_efi_uintn_t desc_size;
grub_efi_uint32_t desc_version;
grub_efi_memory_descriptor_t *mmap_buf;
grub_err_t err;
/* FPSWA. */
query_fpswa ();
boot_param->fpswa = (grub_uint64_t)fpswa;
/* Initrd. */
boot_param->initrd_start = (grub_uint64_t)initrd_mem;
boot_param->initrd_size = (grub_uint64_t)initrd_size;
set_boot_param_console ();
grub_dprintf ("linux", "Jump to %016lx\n", entry);
/* MDT.
Must be done after grub_machine_fini because map_key is used by
exit_boot_services. */
mmap_size = grub_efi_find_mmap_size ();
if (! mmap_size)
return grub_errno;
mmap_buf = grub_efi_allocate_any_pages (page_align (mmap_size) >> 12);
if (! mmap_buf)
return grub_error (GRUB_ERR_IO, "cannot allocate memory map");
err = grub_efi_finish_boot_services (&mmap_size, mmap_buf, &map_key,
&desc_size, &desc_version);
if (err)
return err;
boot_param->efi_memmap = (grub_uint64_t)mmap_buf;
boot_param->efi_memmap_size = mmap_size;
boot_param->efi_memdesc_size = desc_size;
boot_param->efi_memdesc_version = desc_version;
/* See you next boot. */
asm volatile ("mov r28=%1; br.sptk.few %0" :: "b"(entry),"r"(boot_param));
/* Never reach here. */
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_unload (void)
{
free_pages ();
grub_dl_unref (my_mod);
loaded = 0;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_load_elf64 (grub_file_t file, void *buffer, const char *filename)
{
Elf64_Ehdr *ehdr = (Elf64_Ehdr *) buffer;
Elf64_Phdr *phdr;
int i;
grub_uint64_t low_addr;
grub_uint64_t high_addr;
grub_uint64_t align;
grub_uint64_t reloc_offset;
const char *relocate;
if (ehdr->e_ident[EI_MAG0] != ELFMAG0
|| ehdr->e_ident[EI_MAG1] != ELFMAG1
|| ehdr->e_ident[EI_MAG2] != ELFMAG2
|| ehdr->e_ident[EI_MAG3] != ELFMAG3
|| ehdr->e_ident[EI_DATA] != ELFDATA2LSB)
return grub_error(GRUB_ERR_UNKNOWN_OS,
N_("invalid arch-independent ELF magic"));
if (ehdr->e_ident[EI_CLASS] != ELFCLASS64
|| ehdr->e_version != EV_CURRENT
|| ehdr->e_machine != EM_IA_64)
return grub_error (GRUB_ERR_UNKNOWN_OS,
N_("invalid arch-dependent ELF magic"));
if (ehdr->e_type != ET_EXEC)
return grub_error (GRUB_ERR_UNKNOWN_OS,
N_("this ELF file is not of the right type"));
/* FIXME: Should we support program headers at strange locations? */
if (ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize > GRUB_ELF_SEARCH)
return grub_error (GRUB_ERR_BAD_OS, "program header at a too high offset");
entry = ehdr->e_entry;
/* Compute low, high and align addresses. */
low_addr = ~0UL;
high_addr = 0;
align = 0;
for (i = 0; i < ehdr->e_phnum; i++)
{
phdr = (Elf64_Phdr *) ((char *) buffer + ehdr->e_phoff
+ i * ehdr->e_phentsize);
if (phdr->p_type == PT_LOAD)
{
if (phdr->p_paddr < low_addr)
low_addr = phdr->p_paddr;
if (phdr->p_paddr + phdr->p_memsz > high_addr)
high_addr = phdr->p_paddr + phdr->p_memsz;
if (phdr->p_align > align)
align = phdr->p_align;
}
}
if (align < ALIGN_MIN)
align = ALIGN_MIN;
if (high_addr == 0)
return grub_error (GRUB_ERR_BAD_OS, "no program entries");
kernel_pages = page_align (high_addr - low_addr) >> 12;
/* Undocumented on purpose. */
relocate = grub_env_get ("linux_relocate");
if (!relocate || grub_strcmp (relocate, "force") != 0)
{
kernel_mem = grub_efi_allocate_fixed (low_addr, kernel_pages);
reloc_offset = 0;
}
/* Try to relocate. */
if (! kernel_mem && (!relocate || grub_strcmp (relocate, "off") != 0))
{
kernel_mem = allocate_pages (align, kernel_pages, low_addr);
if (kernel_mem)
{
reloc_offset = (grub_uint64_t)kernel_mem - low_addr;
grub_dprintf ("linux", " Relocated at %p (offset=%016lx)\n",
kernel_mem, reloc_offset);
entry += reloc_offset;
}
}
if (! kernel_mem)
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
"cannot allocate memory for OS");
/* Load every loadable segment in memory. */
for (i = 0; i < ehdr->e_phnum; i++)
{
phdr = (Elf64_Phdr *) ((char *) buffer + ehdr->e_phoff
+ i * ehdr->e_phentsize);
if (phdr->p_type == PT_LOAD)
{
grub_dprintf ("linux", " [paddr=%lx load=%lx memsz=%08lx "
"off=%lx flags=%x]\n",
phdr->p_paddr, phdr->p_paddr + reloc_offset,
phdr->p_memsz, phdr->p_offset, phdr->p_flags);
if (grub_file_seek (file, phdr->p_offset) == (grub_off_t)-1)
return grub_errno;
if (grub_file_read (file, (void *) (phdr->p_paddr + reloc_offset),
phdr->p_filesz)
!= (grub_ssize_t) phdr->p_filesz)
{
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
filename);
return grub_errno;
}
if (phdr->p_filesz < phdr->p_memsz)
grub_memset
((char *)(phdr->p_paddr + reloc_offset + phdr->p_filesz),
0, phdr->p_memsz - phdr->p_filesz);
/* Sync caches if necessary. */
if (phdr->p_flags & PF_X)
grub_arch_sync_caches
((void *)(phdr->p_paddr + reloc_offset), phdr->p_memsz);
}
}
loaded = 1;
return 0;
}
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t file = 0;
char buffer[GRUB_ELF_SEARCH];
char *cmdline, *p;
grub_ssize_t len;
int i;
grub_dl_ref (my_mod);
grub_loader_unset ();
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (! file)
goto fail;
len = grub_file_read (file, buffer, sizeof (buffer));
if (len < (grub_ssize_t) sizeof (Elf64_Ehdr))
{
if (!grub_errno)
grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"),
argv[0]);
goto fail;
}
grub_dprintf ("linux", "Loading linux: %s\n", argv[0]);
if (grub_load_elf64 (file, buffer, argv[0]))
goto fail;
len = sizeof("BOOT_IMAGE=") + 8;
for (i = 0; i < argc; i++)
len += grub_strlen (argv[i]) + 1;
len += sizeof (struct ia64_boot_param) + 512; /* Room for extensions. */
boot_param_pages = page_align (len) >> 12;
boot_param = grub_efi_allocate_any_pages (boot_param_pages);
if (boot_param == 0)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY,
"cannot allocate memory for bootparams");
goto fail;
}
grub_memset (boot_param, 0, len);
cmdline = ((char *)(boot_param + 1)) + 256;
/* Build cmdline. */
p = grub_stpcpy (cmdline, "BOOT_IMAGE");
for (i = 0; i < argc; i++)
{
*p++ = ' ';
p = grub_stpcpy (p, argv[i]);
}
cmdline[10] = '=';
*p = '\0';
if (grub_verify_string (cmdline, GRUB_VERIFY_KERNEL_CMDLINE))
goto fail;
boot_param->command_line = (grub_uint64_t) cmdline;
boot_param->efi_systab = (grub_uint64_t) grub_efi_system_table;
grub_errno = GRUB_ERR_NONE;
grub_loader_set (grub_linux_boot, grub_linux_unload, 0);
fail:
if (file)
grub_file_close (file);
if (grub_errno != GRUB_ERR_NONE)
{
grub_efi_free_pages ((grub_efi_physical_address_t) boot_param,
boot_param_pages);
grub_dl_unref (my_mod);
}
return grub_errno;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
if (! loaded)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
goto fail;
}
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
initrd_size = grub_get_initrd_size (&initrd_ctx);
grub_dprintf ("linux", "Loading initrd\n");
initrd_pages = (page_align (initrd_size) >> 12);
initrd_mem = grub_efi_allocate_any_pages (initrd_pages);
if (! initrd_mem)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate pages");
goto fail;
}
grub_dprintf ("linux", "[addr=0x%lx, size=0x%lx]\n",
(grub_uint64_t) initrd_mem, initrd_size);
if (grub_initrd_load (&initrd_ctx, argv, initrd_mem))
goto fail;
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
static grub_err_t
grub_cmd_fpswa (grub_command_t cmd __attribute__ ((unused)),
int argc __attribute__((unused)),
char *argv[] __attribute__((unused)))
{
query_fpswa ();
if (fpswa == NULL)
grub_puts_ (N_("No FPSWA found"));
else
grub_printf (_("FPSWA revision: %x\n"), fpswa->revision);
return GRUB_ERR_NONE;
}
static grub_command_t cmd_linux, cmd_initrd, cmd_fpswa;
GRUB_MOD_INIT(linux)
{
cmd_linux = grub_register_command ("linux", grub_cmd_linux,
N_("FILE [ARGS...]"), N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
N_("FILE"), N_("Load initrd."));
cmd_fpswa = grub_register_command ("fpswa", grub_cmd_fpswa,
"", N_("Display FPSWA version."));
my_mod = mod;
}
GRUB_MOD_FINI(linux)
{
grub_unregister_command (cmd_linux);
grub_unregister_command (cmd_initrd);
grub_unregister_command (cmd_fpswa);
}

View File

@@ -0,0 +1,511 @@
/* linux.c - boot Linux */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2003,2004,2005,2007,2009,2010 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/elf.h>
#include <grub/elfload.h>
#include <grub/loader.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/command.h>
#include <grub/mips/relocator.h>
#include <grub/memory.h>
#include <grub/i18n.h>
#include <grub/lib/cmdline.h>
#include <grub/linux.h>
GRUB_MOD_LICENSE ("GPLv3+");
#pragma GCC diagnostic ignored "-Wcast-align"
/* For frequencies. */
#include <grub/machine/time.h>
#ifdef GRUB_MACHINE_MIPS_LOONGSON
#include <grub/pci.h>
#include <grub/machine/kernel.h>
const char loongson_machtypes[][60] =
{
[GRUB_ARCH_MACHINE_YEELOONG] = "machtype=lemote-yeeloong-2f-8.9inches",
[GRUB_ARCH_MACHINE_FULOONG2F] = "machtype=lemote-fuloong-2f-box",
[GRUB_ARCH_MACHINE_FULOONG2E] = "machtype=lemote-fuloong-2e-unknown"
};
#endif
static grub_dl_t my_mod;
static int loaded;
static grub_size_t linux_size;
static struct grub_relocator *relocator;
static grub_uint8_t *playground;
static grub_addr_t target_addr, entry_addr;
#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
static char *params;
#else
static int linux_argc;
static grub_off_t argv_off;
#ifdef GRUB_MACHINE_MIPS_LOONGSON
static grub_off_t envp_off;
#endif
static grub_off_t rd_addr_arg_off, rd_size_arg_off;
#endif
static int initrd_loaded = 0;
static grub_err_t
grub_linux_boot (void)
{
struct grub_relocator32_state state;
grub_memset (&state, 0, sizeof (state));
/* Boot the kernel. */
state.gpr[1] = entry_addr;
#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
{
grub_err_t err;
grub_relocator_chunk_t ch;
grub_uint32_t *memsize;
grub_uint32_t *magic;
char *str;
err = grub_relocator_alloc_chunk_addr (relocator, &ch,
((16 << 20) - 264),
grub_strlen (params) + 1 + 8);
if (err)
return err;
memsize = get_virtual_current_address (ch);
magic = memsize + 1;
*memsize = grub_mmap_get_lower ();
*magic = 0x12345678;
str = (char *) (magic + 1);
grub_strcpy (str, params);
}
#endif
#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
state.gpr[4] = linux_argc;
state.gpr[5] = target_addr + argv_off;
#ifdef GRUB_MACHINE_MIPS_LOONGSON
state.gpr[6] = target_addr + envp_off;
#else
state.gpr[6] = 0;
#endif
state.gpr[7] = 0;
#endif
state.jumpreg = 1;
grub_relocator32_boot (relocator, state);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_unload (void)
{
grub_relocator_unload (relocator);
grub_dl_unref (my_mod);
#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
grub_free (params);
params = 0;
#endif
loaded = 0;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_load32 (grub_elf_t elf, const char *filename,
void **extra_mem, grub_size_t extra_size)
{
Elf32_Addr base;
int extraoff;
grub_err_t err;
/* Linux's entry point incorrectly contains a virtual address. */
entry_addr = elf->ehdr.ehdr32.e_entry;
linux_size = grub_elf32_size (elf, &base, 0);
if (linux_size == 0)
return grub_errno;
target_addr = base;
/* Pad it; the kernel scribbles over memory beyond its load address. */
linux_size += 0x100000;
linux_size = ALIGN_UP (base + linux_size, 4) - base;
extraoff = linux_size;
linux_size += extra_size;
relocator = grub_relocator_new ();
if (!relocator)
return grub_errno;
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_addr (relocator, &ch,
target_addr & 0x1fffffff,
linux_size);
if (err)
return err;
playground = get_virtual_current_address (ch);
}
*extra_mem = playground + extraoff;
/* Now load the segments into the area we claimed. */
return grub_elf32_load (elf, filename, playground - base, GRUB_ELF_LOAD_FLAGS_NONE, 0, 0);
}
static grub_err_t
grub_linux_load64 (grub_elf_t elf, const char *filename,
void **extra_mem, grub_size_t extra_size)
{
Elf64_Addr base;
int extraoff;
grub_err_t err;
/* Linux's entry point incorrectly contains a virtual address. */
entry_addr = elf->ehdr.ehdr64.e_entry;
linux_size = grub_elf64_size (elf, &base, 0);
if (linux_size == 0)
return grub_errno;
target_addr = base;
/* Pad it; the kernel scribbles over memory beyond its load address. */
linux_size += 0x100000;
linux_size = ALIGN_UP (base + linux_size, 4) - base;
extraoff = linux_size;
linux_size += extra_size;
relocator = grub_relocator_new ();
if (!relocator)
return grub_errno;
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_addr (relocator, &ch,
target_addr & 0x1fffffff,
linux_size);
if (err)
return err;
playground = get_virtual_current_address (ch);
}
*extra_mem = playground + extraoff;
/* Now load the segments into the area we claimed. */
return grub_elf64_load (elf, filename, playground - base, GRUB_ELF_LOAD_FLAGS_NONE, 0, 0);
}
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_elf_t elf = 0;
int size;
void *extra = NULL;
#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
int i;
grub_uint32_t *linux_argv;
char *linux_args;
#endif
grub_err_t err;
#ifdef GRUB_MACHINE_MIPS_LOONGSON
char *linux_envs;
grub_uint32_t *linux_envp;
#endif
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
elf = grub_elf_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (! elf)
return grub_errno;
if (elf->ehdr.ehdr32.e_type != ET_EXEC)
{
grub_elf_close (elf);
return grub_error (GRUB_ERR_UNKNOWN_OS,
N_("this ELF file is not of the right type"));
}
/* Release the previously used memory. */
grub_loader_unset ();
loaded = 0;
#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
size = 0;
#else
/* For arguments. */
linux_argc = argc;
#ifdef GRUB_MACHINE_MIPS_LOONGSON
linux_argc++;
#endif
/* Main arguments. */
size = (linux_argc) * sizeof (grub_uint32_t);
/* Initrd address and size. */
size += 2 * sizeof (grub_uint32_t);
/* NULL terminator. */
size += sizeof (grub_uint32_t);
/* First argument is always "a0". */
size += ALIGN_UP (sizeof ("a0"), 4);
/* Normal arguments. */
for (i = 1; i < argc; i++)
size += ALIGN_UP (grub_strlen (argv[i]) + 1, 4);
#ifdef GRUB_MACHINE_MIPS_LOONGSON
size += ALIGN_UP (sizeof (loongson_machtypes[0]), 4);
#endif
/* rd arguments. */
size += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
size += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
/* For the environment. */
size += sizeof (grub_uint32_t);
size += 4 * sizeof (grub_uint32_t);
size += ALIGN_UP (sizeof ("memsize=XXXXXXXXXXXXXXXXXXXX"), 4)
+ ALIGN_UP (sizeof ("highmemsize=XXXXXXXXXXXXXXXXXXXX"), 4)
+ ALIGN_UP (sizeof ("busclock=XXXXXXXXXX"), 4)
+ ALIGN_UP (sizeof ("cpuclock=XXXXXXXXXX"), 4);
#endif
if (grub_elf_is_elf32 (elf))
err = grub_linux_load32 (elf, argv[0], &extra, size);
else
if (grub_elf_is_elf64 (elf))
err = grub_linux_load64 (elf, argv[0], &extra, size);
else
err = grub_error (GRUB_ERR_BAD_OS, N_("invalid arch-dependent ELF magic"));
grub_elf_close (elf);
if (err)
return err;
#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
/* Create kernel command line. */
size = grub_loader_cmdline_size(argc, argv);
params = grub_malloc (size + sizeof (LINUX_IMAGE));
if (! params)
{
grub_linux_unload ();
return grub_errno;
}
grub_memcpy (params, LINUX_IMAGE, sizeof (LINUX_IMAGE));
grub_create_loader_cmdline (argc, argv, params + sizeof (LINUX_IMAGE) - 1,
size, GRUB_VERIFY_KERNEL_CMDLINE);
#else
linux_argv = extra;
argv_off = (grub_uint8_t *) linux_argv - (grub_uint8_t *) playground;
extra = linux_argv + (linux_argc + 1 + 2);
linux_args = extra;
grub_memcpy (linux_args, "a0", sizeof ("a0"));
*linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground
+ target_addr;
linux_argv++;
linux_args += ALIGN_UP (sizeof ("a0"), 4);
char *params = linux_args;
#ifdef GRUB_MACHINE_MIPS_LOONGSON
{
unsigned mtype = grub_arch_machine;
if (mtype >= ARRAY_SIZE (loongson_machtypes))
mtype = 0;
/* In Loongson platform, it is the responsibility of the bootloader/firmware
to supply the OS kernel with machine type information. */
grub_memcpy (linux_args, loongson_machtypes[mtype],
sizeof (loongson_machtypes[mtype]));
*linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground
+ target_addr;
linux_argv++;
linux_args += ALIGN_UP (sizeof (loongson_machtypes[mtype]), 4);
}
#endif
for (i = 1; i < argc; i++)
{
grub_memcpy (linux_args, argv[i], grub_strlen (argv[i]) + 1);
*linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground
+ target_addr;
linux_argv++;
linux_args += ALIGN_UP (grub_strlen (argv[i]) + 1, 4);
}
*linux_args = '\0';
err = grub_verify_string (params, GRUB_VERIFY_KERNEL_CMDLINE);
if (err)
return err;
/* Reserve space for rd arguments. */
rd_addr_arg_off = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground;
linux_args += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
*linux_argv = 0;
linux_argv++;
rd_size_arg_off = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground;
linux_args += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
*linux_argv = 0;
linux_argv++;
*linux_argv = 0;
extra = linux_args;
#ifdef GRUB_MACHINE_MIPS_LOONGSON
linux_envp = extra;
envp_off = (grub_uint8_t *) linux_envp - (grub_uint8_t *) playground;
linux_envs = (char *) (linux_envp + 5);
grub_snprintf (linux_envs, sizeof ("memsize=XXXXXXXXXXXXXXXXXXXX"),
"memsize=%lld",
(unsigned long long) grub_mmap_get_lower () >> 20);
linux_envp[0] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
+ target_addr;
linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
grub_snprintf (linux_envs, sizeof ("highmemsize=XXXXXXXXXXXXXXXXXXXX"),
"highmemsize=%lld",
(unsigned long long) grub_mmap_get_upper () >> 20);
linux_envp[1] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
+ target_addr;
linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
grub_snprintf (linux_envs, sizeof ("busclock=XXXXXXXXXX"),
"busclock=%d", grub_arch_busclock);
linux_envp[2] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
+ target_addr;
linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
grub_snprintf (linux_envs, sizeof ("cpuclock=XXXXXXXXXX"),
"cpuclock=%d", grub_arch_cpuclock);
linux_envp[3] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
+ target_addr;
linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
linux_envp[4] = 0;
#endif
#endif
grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
initrd_loaded = 0;
loaded = 1;
grub_dl_ref (my_mod);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_size_t size = 0;
void *initrd_src;
grub_addr_t initrd_dest;
grub_err_t err;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
if (!loaded)
return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
if (initrd_loaded)
return grub_error (GRUB_ERR_BAD_ARGUMENT, "only one initrd command can be issued.");
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
{
grub_relocator_chunk_t ch;
err = grub_relocator_alloc_chunk_align (relocator, &ch,
(target_addr & 0x1fffffff)
+ linux_size + 0x10000,
(0x10000000 - size),
size, 0x10000,
GRUB_RELOCATOR_PREFERENCE_NONE, 0);
if (err)
goto fail;
initrd_src = get_virtual_current_address (ch);
initrd_dest = get_physical_target_address (ch) | 0x80000000;
}
if (grub_initrd_load (&initrd_ctx, argv, initrd_src))
goto fail;
#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
{
char *tmp;
tmp = grub_xasprintf ("%s rd_start=0x%" PRIxGRUB_ADDR
" rd_size=0x%" PRIxGRUB_ADDR, params,
initrd_dest, size);
if (!tmp)
goto fail;
grub_free (params);
params = tmp;
}
#else
grub_snprintf ((char *) playground + rd_addr_arg_off,
sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), "rd_start=0x%llx",
(unsigned long long) initrd_dest);
((grub_uint32_t *) (playground + argv_off))[linux_argc]
= target_addr + rd_addr_arg_off;
linux_argc++;
grub_snprintf ((char *) playground + rd_size_arg_off,
sizeof ("rd_size=0xXXXXXXXXXXXXXXXXX"), "rd_size=0x%llx",
(unsigned long long) size);
((grub_uint32_t *) (playground + argv_off))[linux_argc]
= target_addr + rd_size_arg_off;
linux_argc++;
#endif
initrd_loaded = 1;
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
static grub_command_t cmd_linux, cmd_initrd;
GRUB_MOD_INIT(linux)
{
cmd_linux = grub_register_command ("linux", grub_cmd_linux,
0, N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
0, N_("Load initrd."));
my_mod = mod;
}
GRUB_MOD_FINI(linux)
{
grub_unregister_command (cmd_linux);
grub_unregister_command (cmd_initrd);
}

View File

@@ -0,0 +1,393 @@
/* linux.c - boot Linux */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2003,2004,2005,2007,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/elf.h>
#include <grub/elfload.h>
#include <grub/loader.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/ieee1275/ieee1275.h>
#include <grub/command.h>
#include <grub/i18n.h>
#include <grub/memory.h>
#include <grub/lib/cmdline.h>
#include <grub/cache.h>
#include <grub/linux.h>
GRUB_MOD_LICENSE ("GPLv3+");
#define ELF32_LOADMASK (0xc0000000UL)
#define ELF64_LOADMASK (0xc000000000000000ULL)
static grub_dl_t my_mod;
static int loaded;
static grub_addr_t initrd_addr;
static grub_size_t initrd_size;
static grub_addr_t linux_addr;
static grub_addr_t linux_entry;
static grub_size_t linux_size;
static char *linux_args;
typedef void (*kernel_entry_t) (void *, unsigned long, int (void *),
unsigned long, unsigned long);
/* Context for grub_linux_claimmap_iterate. */
struct grub_linux_claimmap_iterate_ctx
{
grub_addr_t target;
grub_size_t size;
grub_size_t align;
grub_addr_t found_addr;
};
/* Helper for grub_linux_claimmap_iterate. */
static int
alloc_mem (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
void *data)
{
struct grub_linux_claimmap_iterate_ctx *ctx = data;
grub_uint64_t end = addr + len;
addr = ALIGN_UP (addr, ctx->align);
ctx->target = ALIGN_UP (ctx->target, ctx->align);
/* Target above the memory chunk. */
if (type != GRUB_MEMORY_AVAILABLE || ctx->target > end)
return 0;
/* Target inside the memory chunk. */
if (ctx->target >= addr && ctx->target < end &&
ctx->size <= end - ctx->target)
{
if (grub_claimmap (ctx->target, ctx->size) == GRUB_ERR_NONE)
{
ctx->found_addr = ctx->target;
return 1;
}
grub_print_error ();
}
/* Target below the memory chunk. */
if (ctx->target < addr && addr + ctx->size <= end)
{
if (grub_claimmap (addr, ctx->size) == GRUB_ERR_NONE)
{
ctx->found_addr = addr;
return 1;
}
grub_print_error ();
}
return 0;
}
static grub_addr_t
grub_linux_claimmap_iterate (grub_addr_t target, grub_size_t size,
grub_size_t align)
{
struct grub_linux_claimmap_iterate_ctx ctx = {
.target = target,
.size = size,
.align = align,
.found_addr = (grub_addr_t) -1
};
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_FORCE_CLAIM))
{
grub_uint64_t addr = target;
if (addr < GRUB_IEEE1275_STATIC_HEAP_START
+ GRUB_IEEE1275_STATIC_HEAP_LEN)
addr = GRUB_IEEE1275_STATIC_HEAP_START
+ GRUB_IEEE1275_STATIC_HEAP_LEN;
addr = ALIGN_UP (addr, align);
if (grub_claimmap (addr, size) == GRUB_ERR_NONE)
return addr;
return (grub_addr_t) -1;
}
grub_machine_mmap_iterate (alloc_mem, &ctx);
return ctx.found_addr;
}
static grub_err_t
grub_linux_boot (void)
{
kernel_entry_t linuxmain;
grub_ssize_t actual;
grub_arch_sync_caches ((void *) linux_addr, linux_size);
/* Set the command line arguments. */
grub_ieee1275_set_property (grub_ieee1275_chosen, "bootargs", linux_args,
grub_strlen (linux_args) + 1, &actual);
grub_dprintf ("loader", "Entry point: 0x%x\n", linux_entry);
grub_dprintf ("loader", "Initrd at: 0x%x, size 0x%x\n", initrd_addr,
initrd_size);
grub_dprintf ("loader", "Boot arguments: %s\n", linux_args);
grub_dprintf ("loader", "Jumping to Linux...\n");
/* Boot the kernel. */
linuxmain = (kernel_entry_t) linux_entry;
linuxmain ((void *) initrd_addr, initrd_size, grub_ieee1275_entry_fn, 0, 0);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_release_mem (void)
{
grub_free (linux_args);
linux_args = 0;
if (linux_addr && grub_ieee1275_release (linux_addr, linux_size))
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot release memory");
if (initrd_addr && grub_ieee1275_release (initrd_addr, initrd_size))
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot release memory");
linux_addr = 0;
initrd_addr = 0;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_unload (void)
{
grub_err_t err;
err = grub_linux_release_mem ();
grub_dl_unref (my_mod);
loaded = 0;
return err;
}
static grub_err_t
grub_linux_load32 (grub_elf_t elf, const char *filename)
{
Elf32_Addr base_addr;
grub_addr_t seg_addr;
grub_uint32_t align;
grub_uint32_t offset;
Elf32_Addr entry;
linux_size = grub_elf32_size (elf, &base_addr, &align);
if (linux_size == 0)
return grub_errno;
/* Pad it; the kernel scribbles over memory beyond its load address. */
linux_size += 0x100000;
/* Linux's entry point incorrectly contains a virtual address. */
entry = elf->ehdr.ehdr32.e_entry & ~ELF32_LOADMASK;
/* Linux's incorrectly contains a virtual address. */
base_addr &= ~ELF32_LOADMASK;
offset = entry - base_addr;
/* On some systems, firmware occupies the memory we're trying to use.
* Happily, Linux can be loaded anywhere (it relocates itself). Iterate
* until we find an open area. */
seg_addr = grub_linux_claimmap_iterate (base_addr & ~ELF32_LOADMASK, linux_size, align);
if (seg_addr == (grub_addr_t) -1)
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't claim memory");
linux_entry = seg_addr + offset;
linux_addr = seg_addr;
/* Now load the segments into the area we claimed. */
return grub_elf32_load (elf, filename, (void *) (seg_addr - base_addr), GRUB_ELF_LOAD_FLAGS_30BITS, 0, 0);
}
static grub_err_t
grub_linux_load64 (grub_elf_t elf, const char *filename)
{
Elf64_Addr base_addr;
grub_addr_t seg_addr;
grub_uint64_t align;
grub_uint64_t offset;
Elf64_Addr entry;
linux_size = grub_elf64_size (elf, &base_addr, &align);
if (linux_size == 0)
return grub_errno;
/* Pad it; the kernel scribbles over memory beyond its load address. */
linux_size += 0x100000;
base_addr &= ~ELF64_LOADMASK;
entry = elf->ehdr.ehdr64.e_entry & ~ELF64_LOADMASK;
offset = entry - base_addr;
/* Linux's incorrectly contains a virtual address. */
/* On some systems, firmware occupies the memory we're trying to use.
* Happily, Linux can be loaded anywhere (it relocates itself). Iterate
* until we find an open area. */
seg_addr = grub_linux_claimmap_iterate (base_addr & ~ELF64_LOADMASK, linux_size, align);
if (seg_addr == (grub_addr_t) -1)
return grub_error (GRUB_ERR_OUT_OF_MEMORY, "couldn't claim memory");
linux_entry = seg_addr + offset;
linux_addr = seg_addr;
/* Now load the segments into the area we claimed. */
return grub_elf64_load (elf, filename, (void *) (grub_addr_t) (seg_addr - base_addr), GRUB_ELF_LOAD_FLAGS_62BITS, 0, 0);
}
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_elf_t elf = 0;
int size;
grub_dl_ref (my_mod);
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto out;
}
elf = grub_elf_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (! elf)
goto out;
if (elf->ehdr.ehdr32.e_type != ET_EXEC && elf->ehdr.ehdr32.e_type != ET_DYN)
{
grub_error (GRUB_ERR_UNKNOWN_OS,
N_("this ELF file is not of the right type"));
goto out;
}
/* Release the previously used memory. */
grub_loader_unset ();
if (grub_elf_is_elf32 (elf))
grub_linux_load32 (elf, argv[0]);
else
if (grub_elf_is_elf64 (elf))
grub_linux_load64 (elf, argv[0]);
else
{
grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid arch-dependent ELF magic"));
goto out;
}
size = grub_loader_cmdline_size(argc, argv);
linux_args = grub_malloc (size + sizeof (LINUX_IMAGE));
if (! linux_args)
goto out;
/* Create kernel command line. */
grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE));
if (grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1,
size, GRUB_VERIFY_KERNEL_CMDLINE))
goto out;
out:
if (elf)
grub_elf_close (elf);
if (grub_errno != GRUB_ERR_NONE)
{
grub_linux_release_mem ();
grub_dl_unref (my_mod);
loaded = 0;
}
else
{
grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
initrd_addr = 0;
loaded = 1;
}
return grub_errno;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_size_t size = 0;
grub_addr_t first_addr;
grub_addr_t addr;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
if (!loaded)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
goto fail;
}
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
first_addr = linux_addr + linux_size;
/* Attempt to claim at a series of addresses until successful in
the same way that grub_rescue_cmd_linux does. */
addr = grub_linux_claimmap_iterate (first_addr, size, 0x100000);
if (addr == (grub_addr_t) -1)
goto fail;
grub_dprintf ("loader", "Loading initrd at 0x%x, size 0x%x\n", addr, size);
if (grub_initrd_load (&initrd_ctx, argv, (void *) addr))
goto fail;
initrd_addr = addr;
initrd_size = size;
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
static grub_command_t cmd_linux, cmd_initrd;
GRUB_MOD_INIT(linux)
{
cmd_linux = grub_register_command ("linux", grub_cmd_linux,
0, N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
0, N_("Load initrd."));
my_mod = mod;
}
GRUB_MOD_FINI(linux)
{
grub_unregister_command (cmd_linux);
grub_unregister_command (cmd_initrd);
}

View File

@@ -0,0 +1,521 @@
/* linux.c - boot Linux */
/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2003, 2004, 2005, 2007, 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/elf.h>
#include <grub/elfload.h>
#include <grub/loader.h>
#include <grub/dl.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/ieee1275/ieee1275.h>
#include <grub/command.h>
#include <grub/i18n.h>
#include <grub/memory.h>
#include <grub/lib/cmdline.h>
#include <grub/linux.h>
GRUB_MOD_LICENSE ("GPLv3+");
static grub_dl_t my_mod;
static int loaded;
/* /virtual-memory/translations property layout */
struct grub_ieee1275_translation {
grub_uint64_t vaddr;
grub_uint64_t size;
grub_uint64_t data;
};
static struct grub_ieee1275_translation *of_trans;
static int of_num_trans;
static grub_addr_t phys_base;
static grub_addr_t grub_phys_start;
static grub_addr_t grub_phys_end;
static grub_addr_t initrd_addr;
static grub_addr_t initrd_paddr;
static grub_size_t initrd_size;
static Elf64_Addr linux_entry;
static grub_addr_t linux_addr;
static grub_addr_t linux_paddr;
static grub_size_t linux_size;
static char *linux_args;
struct linux_bootstr_info {
int len, valid;
char buf[];
};
struct linux_hdrs {
/* All HdrS versions support these fields. */
unsigned int start_insns[2];
char magic[4]; /* "HdrS" */
unsigned int linux_kernel_version; /* LINUX_VERSION_CODE */
unsigned short hdrs_version;
unsigned short root_flags;
unsigned short root_dev;
unsigned short ram_flags;
unsigned int __deprecated_ramdisk_image;
unsigned int ramdisk_size;
/* HdrS versions 0x0201 and higher only */
char *reboot_command;
/* HdrS versions 0x0202 and higher only */
struct linux_bootstr_info *bootstr_info;
/* HdrS versions 0x0301 and higher only */
unsigned long ramdisk_image;
};
static grub_err_t
grub_linux_boot (void)
{
struct linux_bootstr_info *bp;
struct linux_hdrs *hp;
grub_addr_t addr;
hp = (struct linux_hdrs *) linux_addr;
/* Any pointer we dereference in the kernel image must be relocated
to where we actually loaded the kernel. */
addr = (grub_addr_t) hp->bootstr_info;
addr += (linux_addr - linux_entry);
bp = (struct linux_bootstr_info *) addr;
/* Set the command line arguments, unless the kernel has been
built with a fixed CONFIG_CMDLINE. */
if (!bp->valid)
{
int len = grub_strlen (linux_args) + 1;
if (bp->len < len)
len = bp->len;
grub_memcpy(bp->buf, linux_args, len);
bp->buf[len-1] = '\0';
bp->valid = 1;
}
if (initrd_addr)
{
/* The kernel expects the physical address, adjusted relative
to the lowest address advertised in "/memory"'s available
property.
The history of this is that back when the kernel only supported
specifying a 32-bit ramdisk address, this was the way to still
be able to specify the ramdisk physical address even if memory
started at some place above 4GB.
The magic 0x400000 is KERNBASE, I have no idea why SILO adds
that term into the address, but it does and thus we have to do
it too as this is what the kernel expects. */
hp->ramdisk_image = initrd_paddr - phys_base + 0x400000;
hp->ramdisk_size = initrd_size;
}
grub_dprintf ("loader", "Entry point: 0x%lx\n", linux_addr);
grub_dprintf ("loader", "Initrd at: 0x%lx, size 0x%lx\n", initrd_addr,
initrd_size);
grub_dprintf ("loader", "Boot arguments: %s\n", linux_args);
grub_dprintf ("loader", "Jumping to Linux...\n");
/* Boot the kernel. */
asm volatile ("ldx %0, %%o4\n"
"ldx %1, %%o6\n"
"ldx %2, %%o5\n"
"mov %%g0, %%o0\n"
"mov %%g0, %%o2\n"
"mov %%g0, %%o3\n"
"jmp %%o5\n"
"mov %%g0, %%o1\n": :
"m"(grub_ieee1275_entry_fn),
"m"(grub_ieee1275_original_stack),
"m"(linux_addr));
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_release_mem (void)
{
grub_free (linux_args);
linux_args = 0;
linux_addr = 0;
initrd_addr = 0;
return GRUB_ERR_NONE;
}
static grub_err_t
grub_linux_unload (void)
{
grub_err_t err;
err = grub_linux_release_mem ();
grub_dl_unref (my_mod);
loaded = 0;
return err;
}
#define FOUR_MB (4 * 1024 * 1024)
/* Context for alloc_phys. */
struct alloc_phys_ctx
{
grub_addr_t size;
grub_addr_t ret;
};
/* Helper for alloc_phys. */
static int
alloc_phys_choose (grub_uint64_t addr, grub_uint64_t len,
grub_memory_type_t type, void *data)
{
struct alloc_phys_ctx *ctx = data;
grub_addr_t end = addr + len;
if (type != GRUB_MEMORY_AVAILABLE)
return 0;
addr = ALIGN_UP (addr, FOUR_MB);
if (addr + ctx->size >= end)
return 0;
/* OBP available region contains grub. Start at grub_phys_end. */
/* grub_phys_start does not start at the beginning of the memory region */
if ((grub_phys_start >= addr && grub_phys_end < end) ||
(addr > grub_phys_start && addr < grub_phys_end))
{
addr = ALIGN_UP (grub_phys_end, FOUR_MB);
if (addr + ctx->size >= end)
return 0;
}
grub_dprintf("loader",
"addr = 0x%lx grub_phys_start = 0x%lx grub_phys_end = 0x%lx\n",
addr, grub_phys_start, grub_phys_end);
if (loaded)
{
grub_addr_t linux_end = ALIGN_UP (linux_paddr + linux_size, FOUR_MB);
if (addr >= linux_paddr && addr < linux_end)
{
addr = linux_end;
if (addr + ctx->size >= end)
return 0;
}
if ((addr + ctx->size) >= linux_paddr
&& (addr + ctx->size) < linux_end)
{
addr = linux_end;
if (addr + ctx->size >= end)
return 0;
}
}
ctx->ret = addr;
return 1;
}
static grub_addr_t
alloc_phys (grub_addr_t size)
{
struct alloc_phys_ctx ctx = {
.size = size,
.ret = (grub_addr_t) -1
};
grub_machine_mmap_iterate (alloc_phys_choose, &ctx);
return ctx.ret;
}
static grub_err_t
grub_linux_load64 (grub_elf_t elf, const char *filename)
{
grub_addr_t off, paddr, base;
int ret;
linux_entry = elf->ehdr.ehdr64.e_entry;
linux_addr = 0x40004000;
off = 0x4000;
linux_size = grub_elf64_size (elf, 0, 0);
if (linux_size == 0)
return grub_errno;
grub_dprintf ("loader", "Attempting to claim at 0x%lx, size 0x%lx.\n",
linux_addr, linux_size);
paddr = alloc_phys (linux_size + off);
if (paddr == (grub_addr_t) -1)
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
"couldn't allocate physical memory");
ret = grub_ieee1275_map (paddr, linux_addr - off,
linux_size + off, IEEE1275_MAP_DEFAULT);
if (ret)
return grub_error (GRUB_ERR_OUT_OF_MEMORY,
"couldn't map physical memory");
grub_dprintf ("loader", "Loading Linux at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n",
linux_addr, paddr, linux_size);
linux_paddr = paddr;
base = linux_entry - off;
/* Now load the segments into the area we claimed. */
return grub_elf64_load (elf, filename, (void *) (linux_addr - off - base), GRUB_ELF_LOAD_FLAGS_NONE, 0, 0);
}
static grub_err_t
grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_file_t file = 0;
grub_elf_t elf = 0;
int size;
grub_dl_ref (my_mod);
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto out;
}
file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
if (!file)
goto out;
elf = grub_elf_file (file, argv[0]);
if (! elf)
goto out;
if (elf->ehdr.ehdr32.e_type != ET_EXEC)
{
grub_error (GRUB_ERR_UNKNOWN_OS,
N_("this ELF file is not of the right type"));
goto out;
}
/* Release the previously used memory. */
grub_loader_unset ();
if (grub_elf_is_elf64 (elf))
grub_linux_load64 (elf, argv[0]);
else
{
grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid arch-dependent ELF magic"));
goto out;
}
size = grub_loader_cmdline_size(argc, argv);
linux_args = grub_malloc (size + sizeof (LINUX_IMAGE));
if (! linux_args)
goto out;
/* Create kernel command line. */
grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE));
if (grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1,
size, GRUB_VERIFY_KERNEL_CMDLINE))
goto out;
out:
if (elf)
grub_elf_close (elf);
else if (file)
grub_file_close (file);
if (grub_errno != GRUB_ERR_NONE)
{
grub_linux_release_mem ();
grub_dl_unref (my_mod);
loaded = 0;
}
else
{
grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
initrd_addr = 0;
loaded = 1;
}
return grub_errno;
}
static grub_err_t
grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
int argc, char *argv[])
{
grub_size_t size = 0;
grub_addr_t paddr;
grub_addr_t addr;
int ret;
struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
if (argc == 0)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
goto fail;
}
if (!loaded)
{
grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
goto fail;
}
if (grub_initrd_init (argc, argv, &initrd_ctx))
goto fail;
size = grub_get_initrd_size (&initrd_ctx);
addr = 0x60000000;
paddr = alloc_phys (size);
if (paddr == (grub_addr_t) -1)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY,
"couldn't allocate physical memory");
goto fail;
}
ret = grub_ieee1275_map (paddr, addr, size, IEEE1275_MAP_DEFAULT);
if (ret)
{
grub_error (GRUB_ERR_OUT_OF_MEMORY,
"couldn't map physical memory");
goto fail;
}
grub_dprintf ("loader", "Loading initrd at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n",
addr, paddr, size);
if (grub_initrd_load (&initrd_ctx, argv, (void *) addr))
goto fail;
initrd_addr = addr;
initrd_paddr = paddr;
initrd_size = size;
fail:
grub_initrd_close (&initrd_ctx);
return grub_errno;
}
/* Helper for determine_phys_base. */
static int
get_physbase (grub_uint64_t addr, grub_uint64_t len __attribute__ ((unused)),
grub_memory_type_t type, void *data __attribute__ ((unused)))
{
if (type != GRUB_MEMORY_AVAILABLE)
return 0;
if (addr < phys_base)
phys_base = addr;
return 0;
}
static void
determine_phys_base (void)
{
phys_base = ~(grub_uint64_t) 0;
grub_machine_mmap_iterate (get_physbase, NULL);
}
static void
fetch_translations (void)
{
grub_ieee1275_phandle_t node;
grub_ssize_t actual;
int i;
if (grub_ieee1275_finddevice ("/virtual-memory", &node))
{
grub_printf ("Cannot find /virtual-memory node.\n");
return;
}
if (grub_ieee1275_get_property_length (node, "translations", &actual))
{
grub_printf ("Cannot find /virtual-memory/translations size.\n");
return;
}
of_trans = grub_malloc (actual);
if (!of_trans)
{
grub_printf ("Cannot allocate translations buffer.\n");
return;
}
if (grub_ieee1275_get_property (node, "translations", of_trans, actual, &actual))
{
grub_printf ("Cannot fetch /virtual-memory/translations property.\n");
return;
}
of_num_trans = actual / sizeof(struct grub_ieee1275_translation);
for (i = 0; i < of_num_trans; i++)
{
struct grub_ieee1275_translation *p = &of_trans[i];
if (p->vaddr == 0x2000)
{
grub_addr_t phys, tte = p->data;
phys = tte & ~(0xff00000000001fffULL);
grub_phys_start = phys;
grub_phys_end = grub_phys_start + p->size;
grub_dprintf ("loader", "Grub lives at phys_start[%lx] phys_end[%lx]\n",
(unsigned long) grub_phys_start,
(unsigned long) grub_phys_end);
break;
}
}
}
static grub_command_t cmd_linux, cmd_initrd;
GRUB_MOD_INIT(linux)
{
determine_phys_base ();
fetch_translations ();
cmd_linux = grub_register_command ("linux", grub_cmd_linux,
0, N_("Load Linux."));
cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
0, N_("Load initrd."));
my_mod = mod;
}
GRUB_MOD_FINI(linux)
{
grub_unregister_command (cmd_linux);
grub_unregister_command (cmd_initrd);
}

View File

@@ -0,0 +1,24 @@
#include <grub/file.h>
struct grub_linux_initrd_component;
struct grub_linux_initrd_context
{
int nfiles;
struct grub_linux_initrd_component *components;
grub_size_t size;
};
grub_err_t
grub_initrd_init (int argc, char *argv[],
struct grub_linux_initrd_context *ctx);
grub_size_t
grub_get_initrd_size (struct grub_linux_initrd_context *ctx);
void
grub_initrd_close (struct grub_linux_initrd_context *initrd_ctx);
grub_err_t
grub_initrd_load (struct grub_linux_initrd_context *initrd_ctx,
char *argv[], void *target);