You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
13 KiB
479 lines
13 KiB
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
|
From: Matthew Garrett <mjg@redhat.com> |
|
Date: Tue, 10 Jul 2012 11:58:52 -0400 |
|
Subject: [PATCH] Add support for linuxefi |
|
|
|
--- |
|
grub-core/Makefile.core.def | 8 + |
|
grub-core/kern/efi/mm.c | 32 ++++ |
|
grub-core/loader/i386/efi/linux.c | 371 ++++++++++++++++++++++++++++++++++++++ |
|
include/grub/efi/efi.h | 3 + |
|
include/grub/i386/linux.h | 1 + |
|
5 files changed, 415 insertions(+) |
|
create mode 100644 grub-core/loader/i386/efi/linux.c |
|
|
|
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def |
|
index 42443bc00b9..ec46506e39e 100644 |
|
--- a/grub-core/Makefile.core.def |
|
+++ b/grub-core/Makefile.core.def |
|
@@ -1705,6 +1705,14 @@ module = { |
|
enable = x86_64_efi; |
|
}; |
|
|
|
+module = { |
|
+ name = linuxefi; |
|
+ efi = loader/i386/efi/linux.c; |
|
+ efi = lib/cmdline.c; |
|
+ enable = i386_efi; |
|
+ enable = x86_64_efi; |
|
+}; |
|
+ |
|
module = { |
|
name = chain; |
|
efi = loader/efi/chainloader.c; |
|
diff --git a/grub-core/kern/efi/mm.c b/grub-core/kern/efi/mm.c |
|
index be37afd9dc4..ddeca6073f6 100644 |
|
--- a/grub-core/kern/efi/mm.c |
|
+++ b/grub-core/kern/efi/mm.c |
|
@@ -49,6 +49,38 @@ static grub_efi_uintn_t finish_desc_size; |
|
static grub_efi_uint32_t finish_desc_version; |
|
int grub_efi_is_finished = 0; |
|
|
|
+/* Allocate pages below a specified address */ |
|
+void * |
|
+grub_efi_allocate_pages_max (grub_efi_physical_address_t max, |
|
+ grub_efi_uintn_t pages) |
|
+{ |
|
+ grub_efi_status_t status; |
|
+ grub_efi_boot_services_t *b; |
|
+ grub_efi_physical_address_t address = max; |
|
+ |
|
+ if (max > 0xffffffff) |
|
+ return 0; |
|
+ |
|
+ b = grub_efi_system_table->boot_services; |
|
+ status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS, GRUB_EFI_LOADER_DATA, pages, &address); |
|
+ |
|
+ if (status != GRUB_EFI_SUCCESS) |
|
+ return 0; |
|
+ |
|
+ if (address == 0) |
|
+ { |
|
+ /* Uggh, the address 0 was allocated... This is too annoying, |
|
+ so reallocate another one. */ |
|
+ address = max; |
|
+ status = efi_call_4 (b->allocate_pages, GRUB_EFI_ALLOCATE_MAX_ADDRESS, GRUB_EFI_LOADER_DATA, pages, &address); |
|
+ grub_efi_free_pages (0, pages); |
|
+ if (status != GRUB_EFI_SUCCESS) |
|
+ return 0; |
|
+ } |
|
+ |
|
+ return (void *) ((grub_addr_t) address); |
|
+} |
|
+ |
|
/* Allocate pages. Return the pointer to the first of allocated pages. */ |
|
void * |
|
grub_efi_allocate_pages (grub_efi_physical_address_t address, |
|
diff --git a/grub-core/loader/i386/efi/linux.c b/grub-core/loader/i386/efi/linux.c |
|
new file mode 100644 |
|
index 00000000000..b79e6320ba9 |
|
--- /dev/null |
|
+++ b/grub-core/loader/i386/efi/linux.c |
|
@@ -0,0 +1,371 @@ |
|
+/* |
|
+ * GRUB -- GRand Unified Bootloader |
|
+ * Copyright (C) 2012 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/types.h> |
|
+#include <grub/mm.h> |
|
+#include <grub/cpu/linux.h> |
|
+#include <grub/command.h> |
|
+#include <grub/i18n.h> |
|
+#include <grub/lib/cmdline.h> |
|
+#include <grub/efi/efi.h> |
|
+ |
|
+GRUB_MOD_LICENSE ("GPLv3+"); |
|
+ |
|
+static grub_dl_t my_mod; |
|
+static int loaded; |
|
+static void *kernel_mem; |
|
+static grub_uint64_t kernel_size; |
|
+static grub_uint8_t *initrd_mem; |
|
+static grub_uint32_t handover_offset; |
|
+struct linux_kernel_params *params; |
|
+static char *linux_cmdline; |
|
+ |
|
+#define BYTES_TO_PAGES(bytes) (((bytes) + 0xfff) >> 12) |
|
+ |
|
+#define SHIM_LOCK_GUID \ |
|
+ { 0x605dab50, 0xe046, 0x4300, {0xab, 0xb6, 0x3d, 0xd8, 0x10, 0xdd, 0x8b, 0x23} } |
|
+ |
|
+struct grub_efi_shim_lock |
|
+{ |
|
+ grub_efi_status_t (*verify) (void *buffer, grub_uint32_t size); |
|
+}; |
|
+typedef struct grub_efi_shim_lock grub_efi_shim_lock_t; |
|
+ |
|
+static grub_efi_boolean_t |
|
+grub_linuxefi_secure_validate (void *data, grub_uint32_t size) |
|
+{ |
|
+ grub_efi_guid_t guid = SHIM_LOCK_GUID; |
|
+ grub_efi_shim_lock_t *shim_lock; |
|
+ |
|
+ shim_lock = grub_efi_locate_protocol(&guid, NULL); |
|
+ |
|
+ if (!shim_lock) |
|
+ return 1; |
|
+ |
|
+ if (shim_lock->verify(data, size) == GRUB_EFI_SUCCESS) |
|
+ return 1; |
|
+ |
|
+ return 0; |
|
+} |
|
+ |
|
+typedef void(*handover_func)(void *, grub_efi_system_table_t *, struct linux_kernel_params *); |
|
+ |
|
+static grub_err_t |
|
+grub_linuxefi_boot (void) |
|
+{ |
|
+ handover_func hf; |
|
+ int offset = 0; |
|
+ |
|
+#ifdef __x86_64__ |
|
+ offset = 512; |
|
+#endif |
|
+ |
|
+ hf = (handover_func)((char *)kernel_mem + handover_offset + offset); |
|
+ |
|
+ asm volatile ("cli"); |
|
+ |
|
+ hf (grub_efi_image_handle, grub_efi_system_table, params); |
|
+ |
|
+ /* Not reached */ |
|
+ return GRUB_ERR_NONE; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_linuxefi_unload (void) |
|
+{ |
|
+ grub_dl_unref (my_mod); |
|
+ loaded = 0; |
|
+ if (initrd_mem) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)initrd_mem, BYTES_TO_PAGES(params->ramdisk_size)); |
|
+ if (linux_cmdline) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)linux_cmdline, BYTES_TO_PAGES(params->cmdline_size + 1)); |
|
+ if (kernel_mem) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)kernel_mem, BYTES_TO_PAGES(kernel_size)); |
|
+ if (params) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)params, BYTES_TO_PAGES(16384)); |
|
+ return GRUB_ERR_NONE; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), |
|
+ int argc, char *argv[]) |
|
+{ |
|
+ grub_file_t *files = 0; |
|
+ int i, nfiles = 0; |
|
+ grub_size_t size = 0; |
|
+ grub_uint8_t *ptr; |
|
+ |
|
+ 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; |
|
+ } |
|
+ |
|
+ files = grub_zalloc (argc * sizeof (files[0])); |
|
+ if (!files) |
|
+ goto fail; |
|
+ |
|
+ for (i = 0; i < argc; i++) |
|
+ { |
|
+ grub_file_filter_disable_compression (); |
|
+ files[i] = grub_file_open (argv[i]); |
|
+ if (! files[i]) |
|
+ goto fail; |
|
+ nfiles++; |
|
+ size += ALIGN_UP (grub_file_size (files[i]), 4); |
|
+ } |
|
+ |
|
+ initrd_mem = grub_efi_allocate_pages_max (0x3fffffff, BYTES_TO_PAGES(size)); |
|
+ |
|
+ if (!initrd_mem) |
|
+ { |
|
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate initrd")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ params->ramdisk_size = size; |
|
+ params->ramdisk_image = (grub_uint32_t)(grub_uint64_t) initrd_mem; |
|
+ |
|
+ ptr = initrd_mem; |
|
+ |
|
+ for (i = 0; i < nfiles; i++) |
|
+ { |
|
+ grub_ssize_t cursize = grub_file_size (files[i]); |
|
+ if (grub_file_read (files[i], ptr, cursize) != cursize) |
|
+ { |
|
+ if (!grub_errno) |
|
+ grub_error (GRUB_ERR_FILE_READ_ERROR, N_("premature end of file %s"), |
|
+ argv[i]); |
|
+ goto fail; |
|
+ } |
|
+ ptr += cursize; |
|
+ grub_memset (ptr, 0, ALIGN_UP_OVERHEAD (cursize, 4)); |
|
+ ptr += ALIGN_UP_OVERHEAD (cursize, 4); |
|
+ } |
|
+ |
|
+ params->ramdisk_size = size; |
|
+ |
|
+ fail: |
|
+ for (i = 0; i < nfiles; i++) |
|
+ grub_file_close (files[i]); |
|
+ grub_free (files); |
|
+ |
|
+ if (initrd_mem && grub_errno) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)initrd_mem, BYTES_TO_PAGES(size)); |
|
+ |
|
+ return grub_errno; |
|
+} |
|
+ |
|
+static grub_err_t |
|
+grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), |
|
+ int argc, char *argv[]) |
|
+{ |
|
+ grub_file_t file = 0; |
|
+ struct linux_kernel_header lh; |
|
+ grub_ssize_t len, start, filelen; |
|
+ void *kernel; |
|
+ |
|
+ 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]); |
|
+ if (! file) |
|
+ goto fail; |
|
+ |
|
+ filelen = grub_file_size (file); |
|
+ |
|
+ kernel = grub_malloc(filelen); |
|
+ |
|
+ if (!kernel) |
|
+ { |
|
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("cannot allocate kernel buffer")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (grub_file_read (file, kernel, filelen) != filelen) |
|
+ { |
|
+ grub_error (GRUB_ERR_FILE_READ_ERROR, N_("Can't read kernel %s"), argv[0]); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (! grub_linuxefi_secure_validate (kernel, filelen)) |
|
+ { |
|
+ grub_error (GRUB_ERR_INVALID_COMMAND, N_("%s has invalid signature"), argv[0]); |
|
+ grub_free (kernel); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ grub_file_seek (file, 0); |
|
+ |
|
+ grub_free(kernel); |
|
+ |
|
+ params = grub_efi_allocate_pages_max (0x3fffffff, BYTES_TO_PAGES(16384)); |
|
+ |
|
+ if (! params) |
|
+ { |
|
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, "cannot allocate kernel parameters"); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ memset (params, 0, 16384); |
|
+ |
|
+ 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 (0xaa55)) |
|
+ { |
|
+ grub_error (GRUB_ERR_BAD_OS, N_("invalid magic number")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (lh.setup_sects > GRUB_LINUX_MAX_SETUP_SECTS) |
|
+ { |
|
+ grub_error (GRUB_ERR_BAD_OS, N_("too many setup sectors")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (lh.version < grub_cpu_to_le16 (0x020b)) |
|
+ { |
|
+ grub_error (GRUB_ERR_BAD_OS, N_("kernel too old")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (!lh.handover_offset) |
|
+ { |
|
+ grub_error (GRUB_ERR_BAD_OS, N_("kernel doesn't support EFI handover")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ linux_cmdline = grub_efi_allocate_pages_max(0x3fffffff, |
|
+ BYTES_TO_PAGES(lh.cmdline_size + 1)); |
|
+ |
|
+ if (!linux_cmdline) |
|
+ { |
|
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate cmdline")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ grub_memcpy (linux_cmdline, LINUX_IMAGE, sizeof (LINUX_IMAGE)); |
|
+ grub_create_loader_cmdline (argc, argv, |
|
+ linux_cmdline + sizeof (LINUX_IMAGE) - 1, |
|
+ lh.cmdline_size - (sizeof (LINUX_IMAGE) - 1)); |
|
+ |
|
+ lh.cmd_line_ptr = (grub_uint32_t)(grub_uint64_t)linux_cmdline; |
|
+ |
|
+ handover_offset = lh.handover_offset; |
|
+ |
|
+ start = (lh.setup_sects + 1) * 512; |
|
+ len = grub_file_size(file) - start; |
|
+ |
|
+ kernel_mem = grub_efi_allocate_pages(lh.pref_address, |
|
+ BYTES_TO_PAGES(lh.init_size)); |
|
+ |
|
+ if (!kernel_mem) |
|
+ kernel_mem = grub_efi_allocate_pages_max(0x3fffffff, |
|
+ BYTES_TO_PAGES(lh.init_size)); |
|
+ |
|
+ if (!kernel_mem) |
|
+ { |
|
+ grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("can't allocate kernel")); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (grub_file_seek (file, start) == (grub_off_t) -1) |
|
+ { |
|
+ grub_error (GRUB_ERR_BAD_OS, N_("premature end of file %s"), |
|
+ argv[0]); |
|
+ goto fail; |
|
+ } |
|
+ |
|
+ if (grub_file_read (file, kernel_mem, 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_linuxefi_boot, grub_linuxefi_unload, 0); |
|
+ loaded = 1; |
|
+ lh.code32_start = (grub_uint32_t)(grub_uint64_t) kernel_mem; |
|
+ } |
|
+ |
|
+ memcpy(params, &lh, 2 * 512); |
|
+ |
|
+ params->type_of_loader = 0x21; |
|
+ |
|
+ fail: |
|
+ |
|
+ if (file) |
|
+ grub_file_close (file); |
|
+ |
|
+ if (grub_errno != GRUB_ERR_NONE) |
|
+ { |
|
+ grub_dl_unref (my_mod); |
|
+ loaded = 0; |
|
+ } |
|
+ |
|
+ if (linux_cmdline && !loaded) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)linux_cmdline, BYTES_TO_PAGES(lh.cmdline_size + 1)); |
|
+ |
|
+ if (kernel_mem && !loaded) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)kernel_mem, BYTES_TO_PAGES(kernel_size)); |
|
+ |
|
+ if (params && !loaded) |
|
+ grub_efi_free_pages((grub_efi_physical_address_t)params, BYTES_TO_PAGES(16384)); |
|
+ |
|
+ return grub_errno; |
|
+} |
|
+ |
|
+static grub_command_t cmd_linux, cmd_initrd; |
|
+ |
|
+GRUB_MOD_INIT(linuxefi) |
|
+{ |
|
+ cmd_linux = |
|
+ grub_register_command ("linuxefi", grub_cmd_linux, |
|
+ 0, N_("Load Linux.")); |
|
+ cmd_initrd = |
|
+ grub_register_command ("initrdefi", grub_cmd_initrd, |
|
+ 0, N_("Load initrd.")); |
|
+ my_mod = mod; |
|
+} |
|
+ |
|
+GRUB_MOD_FINI(linuxefi) |
|
+{ |
|
+ grub_unregister_command (cmd_linux); |
|
+ grub_unregister_command (cmd_initrd); |
|
+} |
|
diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h |
|
index 489cf9e6da7..9370fd53096 100644 |
|
--- a/include/grub/efi/efi.h |
|
+++ b/include/grub/efi/efi.h |
|
@@ -40,6 +40,9 @@ void EXPORT_FUNC(grub_efi_stall) (grub_efi_uintn_t microseconds); |
|
void * |
|
EXPORT_FUNC(grub_efi_allocate_pages) (grub_efi_physical_address_t address, |
|
grub_efi_uintn_t pages); |
|
+void * |
|
+EXPORT_FUNC(grub_efi_allocate_pages_max) (grub_efi_physical_address_t max, |
|
+ grub_efi_uintn_t pages); |
|
void EXPORT_FUNC(grub_efi_free_pages) (grub_efi_physical_address_t address, |
|
grub_efi_uintn_t pages); |
|
int |
|
diff --git a/include/grub/i386/linux.h b/include/grub/i386/linux.h |
|
index da0ca3b83cd..fc36bdaf367 100644 |
|
--- a/include/grub/i386/linux.h |
|
+++ b/include/grub/i386/linux.h |
|
@@ -139,6 +139,7 @@ struct linux_kernel_header |
|
grub_uint64_t setup_data; |
|
grub_uint64_t pref_address; |
|
grub_uint32_t init_size; |
|
+ grub_uint32_t handover_offset; |
|
} GRUB_PACKED; |
|
|
|
/* Boot parameters for Linux based on 2.6.12. This is used by the setup
|
|
|